零基础开发 era 游戏 #10 业务逻辑(交互反馈)
数值运算和逻辑运算#
运算符优先级#
优先级从高到低排列:
| 类型 | 符号 | 是否代入复合运算 |
|---|---|---|
| 否定运算符 | ~ ! | 否 |
| 算术运算符 | * / % | 是 |
+ - | 是 | |
| 位运算符 | << >> | 是 |
| 比较运算符 | < <= > >= | 否 |
== != | 否 | |
| 逻辑运算符 | & | ^ | 是 |
&& || !& !| ^^ | 否 | |
| 三目运算符 | 判定条件 ? 判定通过 # 判定未通过 | 否 |
否定运算符#
由于 Emuera 的数据类型只有 数值 和 字符串,没有
布尔值 。
因此用 数值 类型的非0/0表示其他语言中的true/false。
! 的本质是「
not 1→0not 0→1
~ 的本质是「按位取反」(~n → -n-1,至于为什么,详见《二进制表达整型》):
~ -1→0~ 0→-1
@SYSTEM_TITLE
CALL TEST_NOT
QUIT
@TEST_NOT
#DIM tmp, 2
WHILE 1
CLEARLINE LINECOUNT
tmp:0 = ~tmp:0
tmp:1 = !tmp:1
PRINTFORMLC ~tmp:0={tmp:0}
PRINTFORML 结果=%tmp:0 ? "true" # "false"%
PRINTFORMLC !tmp:1={tmp:1}
PRINTFORML 结果=%tmp:1 ? "true" # "false"%
INPUTMOUSEKEY
WEND算术运算符#
加 +、减 -、乘 *、除 /、取余 %,有手就行,不多说了。
小数乘法
这里稍微提一下神奇的 TIMES 命令,一个非常实用的 隐藏功能。
在 官方文档 中的确没有提到这个命令,但它确确实实是存在的。
由于本家 / 私家改造版均未使用现代的版本控制系统(指 Git)对源码进行项目管理。
因此这里以 EmueraEE+EM 的源码为例。
在 Emuera/GameProc/Function/BuiltInFunctionCode.cs 注册系统内置函数的枚举值:
| |
在 Emuera/GameProc/Function/Instraction.Child.cs 构造实现具体功能的密封类:
| |
可以看到无论是否启用 严格计算 配置项,第二个参数都是先取为 double 类型。
但前文我们提过「在 era 里,所有数值变量都是整型(int64)」,那么要如何做到呢?
很简单,直接写数值本身就行了:
@SYSTEM_TITLE
#DIM num
WHILE 1
PRINTL 请输入数值:
INPUT
SIF RESULT == 0
CONTINUE
num = RESULT
PRINTFORML 输入的数值 num = {num} 即将提升 25\%(之后自动取整)
;num = num * 5 / 4 ; 等效写法
TIMES num, 1.25
PRINTFORML 提升 25\% 并向下取整后 num = {num}
DRAWLINE
WEND
QUIT位运算符#
左移 << 表示将 数值对象 左移 指定位数,高位丢弃,低位补 0。
在一定范围内,每向左移动一位,相当于 * 2。
右移 >> 表示将 数值对象 右移 指定位数,低位丢弃,高位
(C# 是逻辑右移,Emuera 应该也是。)
在一定范围内,每向右移动一位,相当于 / 2。
; 将 2 左移 9 位 = 2 乘以 9 次 2 = 2 的 10 次方 = 1024(十进制) PRINTVL 2 << 9
逻辑运算符#
&按位「与 」|按位「或 」^按位「异或 」^^不按位的「异或」!&不按位的「与非 」!|不按位的「或非 」
有需要请自行查阅相关资料,此处不再赘述。
实在要用、又搞不太明白的话,可以直接吃现成(使用系统内置函数):
GETBIT(num, bit)获取指定数值变量的指定位的值SETBIT num, bit将指定数值变量的指定位设为1
等价于num |= 1 << bitCLEARBIT num, bit将指定数值变量的指定位设为0
等价于num &= ~(1 << bit)INVERTBIT num, bit将指定数值变量的指定位取反
等价于num ^= 1 << bit
逻辑短路
&&不按位的「与」||不按位的「非」
强烈推荐的用法,这个叫做「短路逻辑」运算符。
比如判断 条件1 && 条件2 通过,需要同时满足 条件 1 和 条件 2(与)。
此时若未满足 条件 1,就不会再继续判断 条件 2 了(因为已经不可能通过判定了)。
同理,判断 条件1 || 条件2 通过,需要满足 条件 1 或 条件 2 任一即可(或)。
此时若已满足 条件 1,就不会再继续判断 条件 2 了(因为已经确定通过判定了)。
在逻辑判断中一律使用「短路逻辑」运算符,可以省下大量不必要的运算。
比较运算符#
大于 >、>=、大于 <、<=、相等 ==、不相等 !=,有脑子就行,不多说了。
三目运算符#
三目运算符(也叫三元运算符),即「若……,则……;否则……」。
WHILE 1
PRINTFORML 你丢了一个硬币,是% RAND:2 == 1 ? "正面" # "反面" %。
INPUTMOUSEKEY
WEND我当然知道「写
RAND:2 == 1」其实等价于「写成RAND:2」。
因为RAND:2表示在[0, 2-1]这个范围(闭区间)内随机生成一个数值。
(当然了,还得是整数,Emuera 就没有小数。)
所以RAND:2的结果要么是0,要么是1,可以直接用来作为判断条件。
(0 = false/非0 = true)
简写会更优雅,写全会更符合直觉以便于理解;具体如何选择见仁见智,随你高兴。
字符串可以直接使用 \@ 三元运算表达式 \@ 的格式。
WHILE 1
PRINTSL @"你丢了一个硬币,是% RAND:2 ? "正面" # "反面" %。"
PRINTSL @"你又丢了一个硬币,是\@ RAND:2 ? 正面 # 反面 \@。"
DRAWLINE
INPUTMOUSEKEY
WENDFORM 解析扩展用法#
@SYSTEM_TITLE
CALL TEST_FORM
QUIT
@TEST_FORM
#DIM num = 123456
#DIMS text = "田中脊髓剑"
PRINTFORML [{num}]
PRINTFORML [{num, 10}]
PRINTFORML [{num, 10, LEFT}]
; 若指定的的长度小于实际长度 会被「撑大」到实际长度
PRINTFORML [{num, 2}]
PRINTFORML [%text%]
PRINTFORML [%text, 20%]
PRINTFORML [%text, 20, LEFT%]
PRINTFORML [%text, 3%]
; 实际用途举例
PRINTSL @"% "", 4 %这是最快的首行缩进写法。"
PRINTL 真的,不骗你。其他 Tips#
- 数值变量
++(递增)和--(递减)运算符都是支持的。 - 但我个人不推荐使用,哪怕你用
+=/-=都比这(可读性)强。 - 所以也有
*=、/=、%=、&=、|=、^=……等等写法。
表示「自己和后面的值进行相应操作,再将自己保存为新的运算结果」。 - 关于字符串赋值
=和'=的区别 之前 讲过了。 - 字符串是可以和数值做「乘法
*」运算的,此时表示「重复该字符串多少次」。 - 哦对了,字符串的
+/+=代表「字符串拼接」操作,以防万一还是提一下。
流程控制#
条件判断#
SIF
注意 SIF 的代码块是不需要关闭的,它只会影响紧挨着它的下一行。
WHILE 1
SIF RAND:2
PRINTL * 这行文本只会随机出现(受 SIF 影响)。
PRINTL + 这行文本一定会出现,因为 SIF 只能控制紧挨着它的下一行。
PRINTL + 缩进也不行,跟你说了 Emuera 无视缩进,缩进只是给你自己看的。
PRINTL
INPUTMOUSEKEY
WENDIF
@SYSTEM_TITLE
WHILE 1
CALL TEST_IF_ELSE
INPUTMOUSEKEY
WEND
QUIT
@TEST_IF_ELSE
#DIM rng
IF 1
PRINTL 测试 IF 通过
ENDIF
rng = RAND:2
IF rng
PRINTFORML 测试 ELSE 随机到一 rng={rng}
ELSE
PRINTFORML 测试 ELSE 随机到零 rng={rng}
ENDIF
rng = RAND:3
IF rng == 1
PRINTFORML 测试 ELSEIF 随机到一 rng={rng}
ELSEIF rng == 2
PRINTFORML 测试 ELSEIF 随机到二 rng={rng}
ELSE
PRINTFORML 测试 ELSEIF 随机到零 rng={rng}
ENDIFSELECT
形如 IF ELSEIF ELSEIF ELSE ENDIF 的多条件判断可以写成可读性更强的 SELECT。
@SYSTEM_TITLE
WHILE 1
CALL TEST_SELECTCASE
INPUTMOUSEKEY
WEND
QUIT
@TEST_SELECTCASE
#DIM rng
rng = RAND(1, 21) ; 1 ~ 20
SELECTCASE rng
CASE 20
SETCOLORBYNAME Red
PRINTFORML 特等奖({rng})
CASE IS <= 2 ; 1, 2
SETCOLORBYNAME Yellow
PRINTFORML 一等奖({rng})
CASE 3, 4, 5
SETCOLORBYNAME Blue
PRINTFORML 二等奖({rng})
CASE 6 TO 10 ; 6, 7, 8, 9, 10
SETCOLORBYNAME Green
PRINTFORML 三等奖({rng})
CASEELSE
SETCOLORBYNAME Gray
PRINTFORML 参与奖({rng})
ENDSELECT循环#
所有循环都可以使用:
CONTINUE跳过这一次循环,直接进入下一次循环BREAK终止当前循环
需要注意的是,
BREAK退出循环时,会继续将自变量 + 1(或其他步进值)再退出。
这是和其他大部分语言不一致的地方(大部分语言break会立刻退出当前循环)。
REPEAT
@SYSTEM_TITLE
CALL TEST_REPEAT
QUIT
@TEST_REPEAT
#DIM cnt
#DIM repeat_times = 10
REPEAT repeat_times
cnt += 1
PRINTFORML 总之重复 {repeat_times} 次就行了 ({cnt}/{repeat_times})
PRINTFORML 注意 REPEAT 会自动将 COUNT 作为自变量:cnt={cnt} COUNT={COUNT}
PRINTL
REND可以近似地将 REPEAT, 循环次数 视为 FOR COUNT, 0, 循环次数 的简写。
若使用
VariableSize.csv禁用COUNT后(需要 Emuera.EE v18 以及之后的版本)不能再使用REPEAT命令。
(会报错COUNTが使用禁止変数になっているため、REPEATは使用できません)
FOR
@SYSTEM_TITLE
CALL TEST_FOR
QUIT
@TEST_FOR
#DIM cnt
#DIM start_num = 100
#DIM end_num = 110
FOR cnt, start_num, end_num, 2
PRINTFORML [{cnt}] 从 {start_num} 递增(每次 + 2)到 {end_num} 为止(不包括 {end_num})
NEXT
FOR cnt, 10, 0, -1
PRINTFORML [{cnt}] 从 10 递减(每次 - 1)到 0 为止(不包括 0)
NEXT若省略第四个参数,则默认为「递增(每次 + 1)」。
WHILE
先判断,如果没有达到结束条件,才执行。
@SYSTEM_TITLE
CALL TEST_WHILE
QUIT
@TEST_WHILE
#DIM cnt = 10
WHILE cnt--
PRINTFORML 当前的 cnt={cnt}
WENDLOOP
类似与其他语言里的 do-while 循环。
先执行,再判断是否达到结束条件,因此至少会循环一次。
比起普通的 while 循环来说,也会多循环一次(已经达到结束条件那一次)。
比如本文的例子,同样是从 10 递减到 0:
- 用
WHILE是循环 10 次(9 ~ 0)- 用
LOOP是循环 11 次(10 ~ 0)
@SYSTEM_TITLE
CALL TEST_LOOP
QUIT
@TEST_LOOP
#DIM cnt = 10
DO
PRINTFORML 当前的 cnt={cnt}
LOOP cnt--跳转(GOTO)#
注意:在任何情况下,你都不应该使用 GOTO。
GOTO 可能会唐突地跳转到任何地方,没有任何逻辑可循。
虽然我们不用,但应该知道怎么用,才能看懂别人写的。
$label_name
PRINTL 请输入 quit 退出:
INPUTS
SIF RESULTS != "quit"
GOTO label_name
PRINTL 成功退出