零基础开发 era 游戏 #10 业务逻辑(交互反馈)
数值运算和逻辑运算#
运算符优先级#
优先级从高到低排列:
类型 | 符号 | 是否代入复合运算 |
---|---|---|
否定运算符 | ~ ! | 否 |
算术运算符 | * / % | 是 |
+ - | 是 | |
位运算符 | << >> | 是 |
比较运算符 | < <= > >= | 否 |
== != | 否 | |
逻辑运算符 | & | ^ | 是 |
&& || !& !| ^^ | 否 | |
三目运算符 | 判定条件 ? 判定通过 # 判定未通过 | 否 |
否定运算符#
由于 Emuera 的数据类型只有 数值 和 字符串,没有
布尔值 。
因此用 数值 类型的非0
/0
表示其他语言中的true
/false
。
!
的本质是「
not 1
→0
not 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 << bit
CLEARBIT 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 WEND
FORM 解析扩展用法#
@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 WEND
IF
@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} ENDIF
SELECT
形如 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} WEND
LOOP
类似与其他语言里的 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 成功退出