From beceb8e588105e90aa69ac92323707352544519d Mon Sep 17 00:00:00 2001 From: Tsukimaru Oshawott Date: Mon, 23 Sep 2024 22:18:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E6=94=B9=E5=8F=98=E9=A2=9C=E8=89=B2=E7=9A=84=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20=20(#84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(utils.gd): 初始化 Utils.gd 可将一些常用的函数(如 max_by 等)写入 Utils,方便复用。 * feat(expr_validator): 添加笑脸列表函数及其余修改 - 添加 get_smile_inds() 函数,用于获取表达式中笑脸位置 - 将 check_valid() 改为 check_invalid(),并返回所有非法下标列表, 而不是仅返回部分 - 将 check() 改为 @deprecated * feat(shake_component): 添加 shake_component ShakeComponent 可对 ShakeComponent.attach_node: Node 的 position 应用抖动效果。 默认 attach_node = null,在 _ready 时设置 attach_node 为父节点。 ShakeComponent.shake(amount: float, duration: float) 可 令其抖动 duration 秒,幅度为 amount。 可通过 ShakeComponent.shake_type 设置抖动类型。 * docs(shake_componen.gd): 添加文档 * refactor: 将 card/block 的变色 / 震动逻辑重构 - 将震动逻辑独立到 ShakeComponent 中,交由其处理 - 当设置非法符号 / 金色符号时,可使用 set_is_invalid / set_is_golden 方法 * feat: 添加动态改变颜色的功能 现在当卡牌放置时,若卡牌非法,就会自动让卡牌变红,而不是等到全部放完 才变红。 * style: 小格式修正 --- components/shake_component/shake_component.gd | 38 ++++++++ .../shake_component/shake_component.tscn | 6 ++ levels/base_level/base_level.gd | 97 ++++++++++++------- objects/block/block.gd | 74 +++++++------- objects/block/block.tscn | 16 ++- objects/card/card.gd | 68 ++++++------- objects/card/card.tscn | 10 +- objects/word/word.gd | 2 +- scripts/expr_validator.gd | 60 ++++++++---- scripts/utils.gd | 8 ++ 10 files changed, 251 insertions(+), 128 deletions(-) create mode 100644 components/shake_component/shake_component.gd create mode 100644 components/shake_component/shake_component.tscn create mode 100644 scripts/utils.gd diff --git a/components/shake_component/shake_component.gd b/components/shake_component/shake_component.gd new file mode 100644 index 0000000..99086b7 --- /dev/null +++ b/components/shake_component/shake_component.gd @@ -0,0 +1,38 @@ +## ShakeComponent 可对 ShakeComponent.attach_node: Node 的 position 应用抖动效果。 +## 默认 attach_node = null,在 _ready 时设置 attach_node 为父节点。 +## ShakeComponent.shake(amount: float, duration: float) 可令其抖动 duration 秒,幅度为 amount。 +## 可通过 ShakeComponent.shake_type 设置抖动类型。 + +class_name ShakeComponent extends Node + +@export var shake_type := ShakeType.H_SIN +@export var attached_node: Node = null + +var progress_ratio := 1.0 +var shake_amount := 0.0 +var duration := 0.001 + +enum ShakeType { + H_SIN, ## 水平方向正弦抖动,[code]pos_delta = Vector2(sin(progress_ratio * 2.0 * PI) * shake_amount, 0)[/code]。 +} + +## 返回抖动的位置偏移。 +func get_position_delta(): + match self.shake_type: + ShakeType.H_SIN: + return Vector2(sin(progress_ratio * 2.0 * PI) * shake_amount, 0) + +## 令其抖动 duration 秒,幅度为 amount。 +func shake(amount: float, duration_time: float = 0.001) -> void: + self.progress_ratio = 0.0 + self.shake_amount = amount + self.duration = duration_time + +func _ready(): + if self.attached_node == null: + self.attached_node = self.get_parent() + +func _process(delta: float): + if self.progress_ratio < 1.0: + self.progress_ratio = min(1.0, self.progress_ratio + delta / self.duration) + self.attached_node.position = self.get_position_delta() \ No newline at end of file diff --git a/components/shake_component/shake_component.tscn b/components/shake_component/shake_component.tscn new file mode 100644 index 0000000..1c04444 --- /dev/null +++ b/components/shake_component/shake_component.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://oxlryy260uym"] + +[ext_resource type="Script" path="res://components/shake_component/shake_component.gd" id="1_3k4mj"] + +[node name="ShakeComponent" type="Node"] +script = ExtResource("1_3k4mj") diff --git a/levels/base_level/base_level.gd b/levels/base_level/base_level.gd index 03be371..7007bfb 100644 --- a/levels/base_level/base_level.gd +++ b/levels/base_level/base_level.gd @@ -76,7 +76,7 @@ func init(_chap_id: int, _lvl_id: int) -> void: var new_block: Block = BlockScn.instantiate() - new_block.quest_pos = i + new_block.ind_in_question = i if ch != "." and ch != "_": new_block.set_is_fixed(true) new_block.set_word(ch) @@ -128,46 +128,71 @@ func stage_clear() -> void: func _on_block_occupied_card_changed(_node) -> void: - var block_array = [] - for block: Block in $Blocks.get_children(): + var block_array : Array[Block] + block_array.assign($Blocks.get_children()) + + var sfx_wa_flag := false + + # 获取 expr + for block in block_array: if block.is_empty(): - #print(block.quest_pos, " is not occupied") - return - expr[block.quest_pos] = block.get_word() - block_array.append(block) + expr[block.ind_in_question] = "_" + else: + expr[block.ind_in_question] = block.get_word() - # prints("# expr: ", expr) + prints() + prints("# expr: ", expr) + + # 检查表达式是否合法,并设置 invalid 标志 + var invalid_inds := ExprValidator.check_invalid(expr) + for block in block_array: + if block.ind_in_question in invalid_inds: + if not block.get_is_invalid(): + sfx_wa_flag = true + block.set_is_invalid(true) + else: + block.set_is_invalid(false) - if expr.count("_") == 0: - var info = ExprValidator.check(expr, req_pos) - # prints("expr:", expr) - # prints("info:", info) - - if info[0] != "OK": - SoundManager.play_sfx(sfx_wrong_answer, sfx_wrong_answer_db) + print("# invalid_inds: ", invalid_inds) + + # 检查表达式笑脸列表,并设置 is_golden 标志 + var smile_inds := ExprValidator.get_smile_inds(expr) + for block in block_array: + block.set_is_golden(block.ind_in_question in smile_inds) - if info[0] == "INVALID": - for block: Block in $Blocks.get_children(): - if block.quest_pos in info[1]: - block.shake(false) - elif info[0] == "SMILE_UNSATISFIED": - for block: Block in $Blocks.get_children(): - if block.quest_pos in info[1]: - block.shake(true) - elif info[0] == "NOT_ALWAYS_TRUE": - for block: Block in $Blocks.get_children(): - block.shake(false) + print("# smile_inds: ", smile_inds) + + # 若表达式合法,且已填满,则考虑检查是否满足笑脸要求 + if invalid_inds.size() == 0 and expr.find("_") == -1: + var smile_unsatisfied_inds: Array[int] + smile_unsatisfied_inds.assign(req_pos.filter(func(ind: int): return ind not in smile_inds)) + + # 若不满足,则设置不满足笑脸要求的块为 invalid + if smile_unsatisfied_inds.size() > 0: + for block in block_array: + if block.ind_in_question in smile_unsatisfied_inds: + if not block.get_is_invalid(): + sfx_wa_flag = true + block.set_is_invalid(true) + + # 若满足笑脸要求,则检测是否恒等 else: - # victory - get_tree().current_scene.set_victory(true) - stage_clear() - # print(block_array) - for i in range(len(block_array)): - if i != 0: - if ExprValidator.is_smile(expr[i - 1] + expr[i]): - # print(expr[i-1]+expr[i]) - block_array[i - 1].set_color(ImageLib.get_palette_color_by_name("golden")) - block_array[i].set_color(ImageLib.get_palette_color_by_name("golden")) + + print("# check: ", ExprValidator.check_always_true(expr)) + if not ExprValidator.check_always_true(expr).is_empty(): + # 若不恒等,设置所有块为 invalid + for block in block_array: + if not block.get_is_invalid(): + sfx_wa_flag = true + block.set_is_invalid(true) + + else: + get_tree().current_scene.set_victory(true) + stage_clear() + + + if sfx_wa_flag: + SoundManager.play_sfx(sfx_wrong_answer, sfx_wrong_answer_db) func _input(event: InputEvent): if event is InputEventKey: diff --git a/objects/block/block.gd b/objects/block/block.gd index bba1366..b8f6d3d 100644 --- a/objects/block/block.gd +++ b/objects/block/block.gd @@ -6,20 +6,24 @@ class_name Block extends Area2D ## 当被占用的 [Card] 发生改变后发出。 ## [br][br] ## [param block] 为发出该信号的 [Block] 对象。 -signal occupied_card_changed(block: Block) +signal occupied_card_changed(block: Block) const SHAKE_AMOUNT := 3.0 ## 振动时的振幅(单位为像素)。 +const SHAKE_DURATION := 0.2 ## 振动时的持续时间(单位为秒)。 var is_fixed : bool ## 是否为固定的(即在表达式中已初始化了的)。 var is_golden_frame : bool ## 是否为金色框。 var is_shaking := false ## 是否正在振动。 +var is_invalid := false ## 是否为非法位置。 +var is_golden := false ## 是否为金色字符。 + var occupied_card: Card = null ## 占用的 Card 对象。 -var quest_pos := -1 ## 在表达式中对应字符位置的下标。 +var ind_in_question := -1 ## 在表达式中对应字符位置的下标。 @@ -63,6 +67,37 @@ func get_word() -> String: else: return self.occupied_card.get_word() +func get_is_invalid() -> bool: + return self.is_invalid + +func set_is_invalid(value: bool) -> void: + if get_is_invalid() == value: + return + + self.is_invalid = value + if self.occupied_card != null: + self.occupied_card.set_is_invalid(value) + + if self.is_invalid: + $Word.set_color(ImageLib.get_palette_color_by_name("red")) + + $Word/ShakeComponent.shake(SHAKE_AMOUNT, SHAKE_DURATION) + $GoalFrameSprite/ShakeComponent.shake(SHAKE_AMOUNT, SHAKE_DURATION) + $PitSprite/ShakeComponent.shake(SHAKE_AMOUNT, SHAKE_DURATION) + + else: + $Word.set_color(ImageLib.get_palette_color_by_name("black")) + +func set_is_golden(value: bool) -> void: + if self.is_golden == value: + return + + self.is_golden = value + if self.occupied_card != null: + self.occupied_card.set_is_golden(value) + + if not self.is_invalid: + $Word.set_color(ImageLib.get_palette_color_by_name("golden" if self.is_golden else "black")) ## 设置 [member is_fixed] 为 [param value]。 func set_is_fixed(value: bool) -> void: @@ -90,38 +125,3 @@ func set_color(value: Color) -> void: else: if self.occupied_card != null: self.occupied_card.set_color(value) - - - -## 开启震动。若 [param is_frame_red] 为 [code]true[/code],则将框改为红色。 -func shake(is_frame_red: bool) -> void: - # 开启震动 - $ShakeTimer.start() - self.is_shaking = true - - # 使附着的卡牌也震动 - if self.occupied_card != null: - self.occupied_card.shake(not is_frame_red, SHAKE_AMOUNT, $ShakeTimer.wait_time) # 若框不红,则里面的字要红 - elif not is_frame_red: - $Word.set_color(ImageLib.get_palette_color_by_name("red")) - - if is_frame_red: - $GoalFrameSprite.animation = "red" - - -func _process(_delta): - var offset := 0 - if is_shaking: - var progress = (1 - $ShakeTimer.time_left / $ShakeTimer.wait_time) * 2 * PI - offset = int(sin(progress) * SHAKE_AMOUNT) - $GoalFrameSprite.position.x = offset - $PitSprite.position.x = offset - $Word.position.x = offset - - - -func _on_shake_timer_timeout() -> void: - self.is_shaking = false - $GoalFrameSprite.animation = "default" - $Word.set_color(ImageLib.get_palette_color_by_name("black")) - diff --git a/objects/block/block.tscn b/objects/block/block.tscn index 42035eb..4443f6b 100644 --- a/objects/block/block.tscn +++ b/objects/block/block.tscn @@ -1,11 +1,18 @@ -[gd_scene load_steps=8 format=3 uid="uid://ckjmjwywh78fe"] +[gd_scene load_steps=11 format=3 uid="uid://ckjmjwywh78fe"] [ext_resource type="Script" path="res://objects/block/block.gd" id="1_11h57"] [ext_resource type="PackedScene" uid="uid://cvx7wowcbfo0r" path="res://objects/word/word.tscn" id="2_pjs40"] +[ext_resource type="Shader" path="res://objects/word/word.gdshader" id="3_gn6mn"] [ext_resource type="Texture2D" uid="uid://dvj7bjepxeks0" path="res://objects/block/pit.png" id="3_ye3hk"] +[ext_resource type="PackedScene" uid="uid://oxlryy260uym" path="res://components/shake_component/shake_component.tscn" id="4_vdwdr"] [ext_resource type="Texture2D" uid="uid://cb71r50jw2a3i" path="res://objects/block/goal_frame.png" id="4_yvtjk"] [ext_resource type="Texture2D" uid="uid://cvv532ui6p5gg" path="res://objects/block/goal_frame_red.png" id="5_et5vw"] +[sub_resource type="ShaderMaterial" id="ShaderMaterial_55t0h"] +resource_local_to_scene = true +shader = ExtResource("3_gn6mn") +shader_parameter/color = null + [sub_resource type="RectangleShape2D" id="RectangleShape2D_3if4e"] size = Vector2(22, 22) @@ -32,6 +39,9 @@ animations = [{ script = ExtResource("1_11h57") [node name="Word" parent="." instance=ExtResource("2_pjs40")] +material = SubResource("ShaderMaterial_55t0h") + +[node name="ShakeComponent" parent="Word" instance=ExtResource("4_vdwdr")] [node name="CollisionShape2D" type="CollisionShape2D" parent="."] z_index = 50 @@ -42,12 +52,16 @@ debug_color = Color(0.968627, 0, 0.470588, 0.419608) z_index = -1 texture = ExtResource("3_ye3hk") +[node name="ShakeComponent" parent="PitSprite" instance=ExtResource("4_vdwdr")] + [node name="ShakeTimer" type="Timer" parent="."] wait_time = 0.2 [node name="GoalFrameSprite" type="AnimatedSprite2D" parent="."] sprite_frames = SubResource("SpriteFrames_3dme2") +[node name="ShakeComponent" parent="GoalFrameSprite" instance=ExtResource("4_vdwdr")] + [connection signal="area_entered" from="." to="." method="_on_area_entered"] [connection signal="area_exited" from="." to="." method="_on_area_exited"] [connection signal="timeout" from="ShakeTimer" to="." method="_on_shake_timer_timeout"] diff --git a/objects/card/card.gd b/objects/card/card.gd index d772e21..8dfa07b 100644 --- a/objects/card/card.gd +++ b/objects/card/card.gd @@ -6,6 +6,10 @@ class_name Card extends Area2D @export var sfx_put_down_db : float = 0.0 ## 放下卡牌时的音效音量 +const SHAKE_AMOUNT := Block.SHAKE_AMOUNT +const SHAKE_DURATION := Block.SHAKE_DURATION + + var current_block: Block = null ## 当前所占用的 Block 对象。 @@ -16,6 +20,10 @@ var last_block: Block = null var is_dragging := false ## 是否正在被鼠标拖拽。 var is_shaking := false ## 是否正在震动。 + +var is_invalid := false ## 是否为非法字符。 +var is_golden := false ## 是否为金色字符。 + var is_victory := false ## 是否已通关。 var shake_amount : float ## 震动幅度(单位为像素)。 @@ -74,27 +82,30 @@ func put_down(): queue_free() -## 使 [Card] 开始震动。 [param amount] 和 [param duration] 为震动幅度和震动持续时间。 -## [br][br] -## 若 [param is_letter_red] 为 [code]true[/code],则将字符颜色改为红色。 -func shake(is_letter_red: bool, amount: float, duration: float) -> void: - $ShakeTimer.wait_time = duration - self.shake_amount = amount - - # 开启震动 - $ShakeTimer.start() - self.is_shaking = true - - if is_letter_red: - $Word.set_color(ImageLib.get_palette_color_by_name("red")) - else: - $HighlightSprite.visible = false +## 使 [Card] 开始震动。 +func shake() -> void: + $HighlightSprite/ShakeComponent.shake(SHAKE_AMOUNT, SHAKE_DURATION) + $CardBackSprite/ShakeComponent.shake(SHAKE_AMOUNT, SHAKE_DURATION) + $Word/ShakeComponent.shake(SHAKE_AMOUNT, SHAKE_DURATION) + $Word.set_color(ImageLib.get_palette_color_by_name("red")) ## 设置字符颜色为 [param value]。 func set_color(value: Color) -> void: $Word.set_color(value) # 设置文字颜色 +## 设置非法标志为 [param value]。 +func set_is_invalid(value: bool) -> void: + self.is_invalid = value + if self.is_invalid: + self.shake() + self.set_color(self.get_color()) + +## 设置金色标志为 [param value]。 +func set_is_golden(value: bool) -> void: + self.is_golden = value + self.set_color(self.get_color()) + ## 若 [param v] 为 [code]true[/code],则进行通关时的处理。 func set_victory(v: bool): @@ -122,6 +133,13 @@ func set_word(value: String) -> void: func get_word() -> String: return $Word.get_word() +func get_color() -> Color: + if self.is_invalid: + return ImageLib.get_palette_color_by_name("red") + elif self.is_golden: + return ImageLib.get_palette_color_by_name("golden") + else: + return ImageLib.get_palette_color_by_name("black") func _ready(): @@ -131,19 +149,6 @@ func _ready(): func _process(_delta: float) -> void: if self.is_dragging: # 如果正在拖拽 self.global_position = get_global_mouse_position().round() # 将卡牌位置设置为鼠标位置的全局位置,四舍五入取整 - - var offset := 0 - if self.is_shaking: - # 如果正在震动,则设置震动偏移量为 sin(progress) * self.shake_amount - # 其中 progress in [0, 2 * PI] 是震动进度,随着时间的推移逐渐增加 - # self.shake_amount 是震动幅度 - var progress = (1 - $ShakeTimer.time_left / $ShakeTimer.wait_time) * 2 * PI - offset = int(sin(progress) * self.shake_amount) - - # 设置 Sprite 的震动偏移量 - $CardBackSprite.position.x = offset - $HighlightSprite.position.x = offset - $Word.position.x = offset func _input_event(_viewport: Object, event: InputEvent, _shape_idx: int) -> void: @@ -168,10 +173,5 @@ func _on_mouse_exited(): func _on_tree_exiting(): - if self.current_block != null: + if self.current_block != null and self.current_block.occupied_card == self: self.current_block.set_card(null) - - -func _on_shake_timer_timeout(): - self.is_shaking = false - $Word.set_color(ImageLib.get_palette_color_by_name("black")) diff --git a/objects/card/card.tscn b/objects/card/card.tscn index ed92501..67a3a23 100644 --- a/objects/card/card.tscn +++ b/objects/card/card.tscn @@ -1,10 +1,11 @@ -[gd_scene load_steps=12 format=3 uid="uid://b223s12rqgt4h"] +[gd_scene load_steps=13 format=3 uid="uid://b223s12rqgt4h"] [ext_resource type="Script" path="res://objects/card/card.gd" id="1_0bviv"] [ext_resource type="PackedScene" uid="uid://cvx7wowcbfo0r" path="res://objects/word/word.tscn" id="1_ns36n"] [ext_resource type="Texture2D" uid="uid://mmtuv34pq4t2" path="res://objects/card/card1.png" id="2_m034v"] [ext_resource type="Texture2D" uid="uid://ct5w4n7kylfuc" path="res://objects/card/card2.png" id="3_2vd1f"] [ext_resource type="Texture2D" uid="uid://c6f6j4aln0qvp" path="res://objects/card/card3.png" id="4_wleyv"] +[ext_resource type="PackedScene" uid="uid://oxlryy260uym" path="res://components/shake_component/shake_component.tscn" id="6_qmffj"] [ext_resource type="Shader" path="res://objects/word/word.gdshader" id="7_4vbp5"] [ext_resource type="Texture2D" uid="uid://pmapbo480in1" path="res://objects/card/card_highlight_border.png" id="7_kix4t"] [ext_resource type="AudioStream" uid="uid://cj4v8ehypq3sk" path="res://objects/card/put_down.wav" id="7_u4ylf"] @@ -43,12 +44,18 @@ sfx_put_down_db = 4.0 [node name="CardBackSprite" type="AnimatedSprite2D" parent="."] sprite_frames = SubResource("SpriteFrames_fo0r0") +[node name="ShakeComponent" parent="CardBackSprite" instance=ExtResource("6_qmffj")] + [node name="HighlightSprite" type="Sprite2D" parent="."] texture = ExtResource("7_kix4t") +[node name="ShakeComponent" parent="HighlightSprite" instance=ExtResource("6_qmffj")] + [node name="Word" parent="." instance=ExtResource("1_ns36n")] material = SubResource("ShaderMaterial_7hcku") +[node name="ShakeComponent" parent="Word" instance=ExtResource("6_qmffj")] + [node name="CollisionShape2D" type="CollisionShape2D" parent="."] z_index = 15 shape = SubResource("RectangleShape2D_gu6l6") @@ -63,4 +70,5 @@ one_shot = true [connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"] [connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"] +[connection signal="tree_exiting" from="." to="." method="_on_tree_exiting"] [connection signal="timeout" from="ShakeTimer" to="." method="_on_shake_timer_timeout"] diff --git a/objects/word/word.gd b/objects/word/word.gd index b4b9b8c..3f1f4dc 100644 --- a/objects/word/word.gd +++ b/objects/word/word.gd @@ -83,4 +83,4 @@ func set_victory(v: bool) -> void: func _ready(): update_animation() - set_color(self.color) \ No newline at end of file + set_color(self.color) diff --git a/scripts/expr_validator.gd b/scripts/expr_validator.gd index 4ca7dc4..1dfd2e7 100644 --- a/scripts/expr_validator.gd +++ b/scripts/expr_validator.gd @@ -61,7 +61,7 @@ enum CharType { } static func is_pair_valid(ch1: String, ch2: String) -> bool: ## 判断相邻字符 [param ch1] 和 [param ch2] 是否合法。 - return (ch1 + ch2) in ["<=", ">=", "<>"] or IS_PAIR_VALID[get_char_type(ch1)][get_char_type(ch2)] + return (ch1 + ch2) in ["<=", ">=", "<>"] or IS_PAIR_VALID[get_char_type(ch1)][get_char_type(ch2)] ## 获取 [param ch] 的 [enum CharType]。 static func get_char_type(ch: String) -> CharType: @@ -226,36 +226,47 @@ static func calculate_value(expr: String, var_values: Dictionary) -> bool: ## 判断表达式 [param expr] 是否合法。 ## [br][br] -## 合法返回空数组 [code][][/code],否则返回不合法的下标列表。 -static func check_valid(expr: String) -> Array: - if get_char_type(expr[0]) != CharType.BRACL and get_char_type(expr[0]) not in [CharType.VAR, CharType.CONST]: - return [0] - if get_char_type(expr[len(expr) - 1]) != CharType.BRACR and get_char_type(expr[len(expr) - 1]) not in [CharType.VAR, CharType.CONST]: - return [len(expr) - 1] +## 返回不合法的下标列表。 +static func check_invalid(expr: String) -> Array: + var result: Array[int] = [] + + # 特判开头及尾部是否合法 + if expr[0] != '_' and expr[0] != '(' and get_char_type(expr[0]) not in [CharType.VAR, CharType.CONST]: + result.append(0) + if expr[len(expr) - 1] != '_' and expr[len(expr) - 1] != ')' and get_char_type(expr[len(expr) - 1]) not in [CharType.VAR, CharType.CONST]: + result.append(len(expr) - 1) + + # 测试相邻字符是否合法 for i in range(len(expr) - 1): - if not is_pair_valid(expr[i], expr[i + 1]): - return [i, i + 1] + if expr[i] != '_' and expr[i + 1] != '_' and not is_pair_valid(expr[i], expr[i + 1]): + result.append(i) + result.append(i + 1) + # 测试括号匹配 var brac_sum := 0 for i in range(len(expr)): - if expr[i] == "(": + if expr[i] == '(': brac_sum += 1 - elif expr[i] == ")": + elif expr[i] == ')': if brac_sum == 0: - return [i] + result.append(i) brac_sum -= 1 if brac_sum > 0: for i in range(len(expr) - 1, -1, -1): - if expr[i] == "(": - return [i] - return [] + if expr[i] == '(' and brac_sum > 0: + brac_sum -= 1 + result.append(i) + + result.sort() + print(result) + return Utils.array_unique(result) ## 判断表达式 [param expr] 是否满足笑脸要求。 ## [br][br] -## 合法返回空数组 [code][][/code],否则返回不为笑脸的字符下标列表。 +## 返回不为笑脸的字符下标列表。 static func check_smile(expr: String, req_pos: Array) -> Array: - var res := [] + var res: Array[int] = [] for i in req_pos: if (i == 0 or not is_smile(expr.substr(i - 1, 2))) and (i == len(expr) - 1 or not is_smile(expr.substr(i, 2))): res.append(i) @@ -297,12 +308,15 @@ static func check_always_true(expr: String) -> Dictionary: ## 若表达式不恒为正,返回 [code]["NOT_ALWAYS_TRUE", var_values][/code],其中 [code]var_values: Dictionary[String, bool][/code] 是一种使得表达式为假的变量取值。 ## [br][br] ## 否则符合要求,返回 [code]["OK", 200][/code]。 +## +## @deprecated: 由于需要同时获得多种不合法相关的信息,故在 BaseLevel 中不再使用。使用该函数反而会增加代码复杂度(需要大量关于 "SMILE_UNSATISFIED" 这样的魔术字符串的判断),所以建议直接使用 check_xxx 代替。 static func check(expr: String, req_pos: Array) -> Array: + push_warning("ExprValidator.check is deprecated.") var tmp # 判断合法性 - tmp = check_valid(expr) + tmp = check_invalid(expr) if tmp != []: return ["INVALID", tmp] @@ -319,6 +333,16 @@ static func check(expr: String, req_pos: Array) -> Array: return ["OK", 200] +## 返回表达式字符串 [param expr] 中笑脸的下标列表。 +static func get_smile_inds(expr: String) -> Array[int]: + var res: Array[int] = [] + for i in range(len(expr) - 1): + if expr[i] != '_' and expr[i + 1] != '_' and is_smile(expr.substr(i, 2)): + if i not in res: + res.append(i) + res.append(i + 1) + return res + # static func test(): #check("q*Pq*bd*b=D", []) # check("0=P<>P", []) diff --git a/scripts/utils.gd b/scripts/utils.gd new file mode 100644 index 0000000..811696c --- /dev/null +++ b/scripts/utils.gd @@ -0,0 +1,8 @@ +class_name Utils + +static func array_unique(arr: Array) -> Array: + var res := [] + for i in arr: + if i not in res: + res.append(i) + return res \ No newline at end of file