> LeekScript 教程
写程序时,最重要的是把代码组织好。为此,你目前学过的只有注释,差不多就这些了……不过我们马上就来改变这一点:接下来要讲函数,它是编程中最重要的概念之一,你很快就会明白为什么。其实你已经见过、甚至用过函数了,就在内置函数那一章。
函数就是一串指令,仅此而已。有了它,就不用再重复写同样的代码,只要调用这个函数,它就会执行这些指令。为了让函数更通用,可以给它传入参数,比如你想攻击的韭葱、本回合剩下的 MP 数等等。此外,函数还可以返回一个值,比如一个包含你的韭葱能到达的所有格子的数组。下面是函数的原型,也就是函数的签名:
NomDeLaFonction ( parametre1, parametre2, ..., parametreN ) : Retour
原型是对函数的描述:它的名字、它的返回值(如果有的话),以及它的参数(参数也是可选的)。 函数的定义,就是函数里包含的全部指令。 所有内置函数的原型都可以在文档 中查到。在左侧按主题排列的函数列表里,选中“武器”分类下的“UseWeapon”,就会看到下面的内容: !UseWeapon-Mod
调用一个函数时,“主”程序会暂停,转而执行存放在函数里的程序。等这个函数执行完,“主”程序再从刚才停下的地方继续往下执行。
函数也有“声明”的说法。LS 为你准备了现成的内置函数,比如“getNearestEnemy / useWeapon / ……”。不过你也可以自己创建函数,语法如下:
function NomDeLaFontion ( parametre1, parametre2, ..., parametreN ) { /* 指令 */ }
函数里并不是什么都能做:禁止在函数中声明新的函数,也禁止在函数中声明全局变量!
为了更直观,我们来创建一个非常简单的函数,它的作用是:向地图中央靠近,然后使用一块护盾芯片和一块治疗芯片。它的原型如下: (注意,原型不用写进代码里,顶多写在注释里,用来说明你的函数)
ArmorAndCare()
确实再简单不过了:这个函数不接收参数,也不返回任何值!
不过它还缺一个定义,定义如下:
function ArmorAndCare(){ moveTowardCell(306); useChip(CHIP_ARMOR, getEntity()); useChip(CHIP_CURE, getEntity()); }
调用这个函数的方式和调用其他函数一样:
ArmorAndCare(); // 向地图中央靠近,然后使用 ARMOR 和 CURE
有一点很重要:函数无法访问外部的变量,全局变量除外。你可以这样想象:调用函数时,它“生活”在自己的世界里,唯一能接触到的只有全局变量和其他函数。
为了说明函数的参数和返回值是怎么回事,我们来看这个例子:
// 原型:getCellDistanceTo(leek) : (数字) 我的韭葱与参数中那根韭葱之间的距离 function getCellDistanceTo(leek){ return getCellDistance(getCell(), getCell(leek)); }
根据原型,这个函数接收一根韭葱(它的 ID)作为参数,返回这根韭葱与你自己的韭葱之间的距离。 参数的个数(几乎)没有上限,只要用逗号把各个参数隔开就行。 函数通过关键字“return”返回值。在这个例子中,函数“getCellDistance”返回一个数字,我们直接把它返回出去。函数一遇到“return”就会停止,回到调用它的地方继续执行。顺便给感兴趣的人提一句:减少函数参数个数的做法叫作“柯里化”。如果你想提前结束函数而不返回任何值,只要写一个不带值的关键字“return”就行。
在上面的例子中,调用函数时传进去的数据都会被复制一份。具体过程如下:
function getCellDistanceTo(leek){ return getCellDistance(getCell(), getCell(leek)); }
var enemy = getNearestEnemy();
getCellDistanceTo(enemy );
// 1 - 调用 getNearestEnemy,它返回一根韭葱的 ID(例如:2) // 2 - enemy 现在的值是 '2' // 3 - 调用 getCellDistanceTo,它接收一个参数(这个例子里是 enemy,也就是 2) // 4 - 在函数 getCellDistanceTo 中:先执行 'leek = 2;',再执行函数的定义 // 5 - 调用 getCellDistance:复制 'getCell()'(的返回值)和 'getCell(leek)'(也是返回值) // 6 - 返回两根韭葱之间的距离
这次复制意味着什么?如果在“getCellDistanceTo”里修改“leek”,我们的变量“enemy”不会跟着改变。
我们再举一个例子,更好地说明按引用传递:
// 原型 // canDivide (result, (数字) a, (数字) b) : (布尔值) 如果 'a' 能被 'b' 除,返回 True,并把结果存进 'result'
// 定义 function canDivide (result, a, b) { result = 0; if (b === 0) return false; // 不能除以零 // 不需要 'else',因为 b === 0 时函数已经结束了 result = a / b; return true; }
目前这种叫作“按值传递”,因为调用这个函数时,所有参数都会被复制。 要按引用传递,需要在(定义中的)参数前面加一个“@”:
function canDivide2 (@result, a, b) { result = 0; if (b === 0) return false; // 不能除以零 // 不需要 'else',因为 b === 0 时函数已经结束了 result = a / b; return true; }
这样一来,修改 result 时,调用函数时传进去的那个变量也会跟着改变。我们来测试一下这两个函数:
var result;
if (canDivide(result, 5, 2)) // 按值传递的函数 debug("5 可以除以 2,结果是:" + result); else debug("5 不能除以 2!");
if (canDivide2(result, 5, 2)) // 按引用传递的函数 debug("5 可以除以 2,结果是:" + result); else debug("5 不能除以 2!"); /* 测试后会得到: "5 可以除以 2,结果是:null" 然后是 "5 可以除以 2,结果是:2.5" */
@ 不仅能用在参数上,如果你想进一步了解 @ 的行为,推荐你看看这个页面:@的行为
在 Leekscript 中,函数是一等对象。因此,function name([parameters]) {[code]} 这种形式的函数声明可以看作一种特殊语法。这一点在作用域上尤其明显:语言会把用这种方式声明的函数单独对待,在它们自己的作用域里,只能访问全局作用域中的对象(包括它们自己)。 把函数当作普通的一等对象来操作,能让我们用更有意思的方式看待函数。回想一下,数字、字符串、数组都能做哪些事:
动手才能学会。目前我们先重新实现几个接收“回调”(callback,其实就是一个函数,以后我们尽量不用“回调”这个词)的 arrayX 函数。作为参数的函数只接收一个或两个参数(我们不处理关联数组),而且为了给后面“作为返回值的函数”一节做准备,我们会把参数换一种顺序排列。建议从 iter 开始。
filter : (elem -> Bool, [elem]) -> [elem]:这里我们要重新构建一个数组,只保留那些让函数返回 true 的元素(把函数作用于元素上:f(xs[i]))。例如,传入 isSummon 和 getAliveAllies() 作为参数(filter(isSummon, getAliveAllies())),就会得到存活的友方球茎列表。接下来你可以去做 partition。foldl : ((acc, elem) -> acc, acc, [elem]) -> acc:这里我们要从头到尾遍历并“消耗”一个数组。先把函数作用于累加器和第一个元素(f(base, xs[0])),再把函数作用于得到的结果和第二个元素,以此类推,直到遍历完整个数组。最后返回最终结果。foldr : ((elem, acc) -> acc, acc, [elem]) -> acc:这里我们要从尾到头遍历并“消耗”一个数组。先把函数作用于最后一个元素和累加器(f(xs[n], base)),再把函数作用于倒数第二个元素和得到的结果,以此类推,直到遍历完整个数组。最后返回最终结果。iter : (elem -> (), [elem]) -> ():这里我们只是把函数作用于数组的每个元素。和作为参数的那个函数一样,我们什么也不返回(或者返回 null)。这是最容易实现的函数,不妨从它开始,然后再做 map。map : (elem -> ret, [elem]) -> [ret]:这里我们要重新构建一个数组,里面是经过所给函数转换后的对象。如果传入 getLife 和 getAliveAllies() 作为参数,就会得到我方团队的生命值列表。接着,想知道团队的生命值总和,可以把它交给 sum;想了解团队的平均健康状况,可以交给 average;想知道有没有人急需治疗,可以交给 arrayMin。实现了这个函数之后,建议你接着写 filter。:这里要做的和 filter 一样,只不过不满足谓词的元素不会被丢掉,而是放进第二个数组。然后我们把这两个数组放在一个数组里返回:第一个是满足谓词的元素列表,第二个是其余元素的列表。用和 filter` 相同的例子,索引 0 处是友方球茎列表,索引 1 处是友方韭葱列表。sort : (elem -> Ord, [elem]) -> [elem]:数组排序太复杂,超出了本文的范围。如果以后有专门讲这个主题的文章,会在这里加上链接。Impossible de charger les données du jeu.
Vérifiez votre connexion et réessayez.