Skip to content

Commit

Permalink
Added to_json() for other, lose condition, input replay
Browse files Browse the repository at this point in the history
Added Utils.to_bool() utility as a workaround for Godot #27529
(godotengine/godot#27529).
  • Loading branch information
Poobslag committed Sep 30, 2021
1 parent 069afcd commit 7886da4
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 64 deletions.
4 changes: 2 additions & 2 deletions project/src/main/puzzle/input-replay.gd
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ func from_json_array(json: Array) -> void:


func to_json_array() -> Array:
return []
return action_timings.keys()


func is_default() -> bool:
return false
return action_timings.empty()
121 changes: 101 additions & 20 deletions project/src/main/puzzle/level/array-rules-serializer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,34 @@ class_name ArrayRulesSerializer
class RuleSerializer:
var target: Object
var name: String

# default value if the rule is unspecified
var default

# implied value if the rule is specified without a value
var implied

func _init(init_target: Object, init_name: String) -> void:
target = init_target
name = init_name


func to_json_string() -> String:
return ""
var result: String
if target.get(name) == implied:
result = name
else:
result = "%s %s" % [name, target.get(name)]
return result


func from_json_string(json: String) -> void:
pass
var split := json.split(" ")
match split.size():
1:
target.set(name, implied)
2:
target.set(name, json.split(" ")[1])


func is_default() -> bool:
Expand All @@ -27,16 +42,57 @@ class RuleSerializer:


class BoolRuleSerializer extends RuleSerializer:
func _init(init_target: Object, init_name: String).(init_target, init_name) -> void:
var _false_string: String

func _init(init_target: Object, init_name: String, init_false_string: String = "").(init_target, init_name) -> void:
default = false
implied = true
_false_string = init_false_string


func from_json_string(json: String) -> void:
target.set(name, true)
var split := json.split(" ")
match split.size():
1:
match split[0]:
name:
# parse positive string like 'clear_on_finish'
target.set(name, true)
_false_string:
# parse negative string like 'no_clear_on_finish'
target.set(name, false)
_:
push_warning("Unrecognized: %s" % [split[0]])
2:
match split[0]:
name:
# parse compound string like 'clear_on_finish true'
target.set(name, false if split[1] in ["False", "false"] else true)
print("71: set %s to %s (%s)" % [name, Utils.to_bool(split[1]), target.get(name)])
_false_string:
# parse inverted compound string like 'no_clear_on_finish false' (why...)
target.set(name, false if split[1] in ["False", "false"] else true)
print("74: set %s to %s (%s)" % [name, Utils.to_bool(split[1]), target.get(name)])
_:
push_warning("Unrecognized: %s" % [split[0]])


func to_json_string() -> String:
return name
var result: String
if target.get(name) == true:
# return positive string like 'clear_on_finish'
result = name
elif _false_string:
# return negative string like 'no_clear_on_finish'
result = _false_string
else:
# return compound string like 'clear_on_finish false'
result = "%s %s" % [name, target.get(name)]
return result


func keys() -> Array:
return [name, _false_string] if _false_string else [name]


class IntRuleSerializer extends RuleSerializer:
Expand All @@ -45,11 +101,17 @@ class IntRuleSerializer extends RuleSerializer:


func from_json_string(json: String) -> void:
target.set(name, int(json.split(" ")[1]))


func to_json_string() -> String:
return "%s %s" % [name, str(target.get(name))]
var split := json.split(" ")
match split.size():
1:
target.set(name, implied)
2:
target.set(name, int(json.split(" ")[1]))


class StringRuleSerializer extends RuleSerializer:
func _init(init_target: Object, init_name: String).(init_target, init_name) -> void:
default = ""


class EnumRuleSerializer extends RuleSerializer:
Expand All @@ -58,18 +120,29 @@ class EnumRuleSerializer extends RuleSerializer:
func _init(init_target: Object, init_name: String, init_enum_dict).(init_target, init_name) -> void:
_enum_dict = init_enum_dict
default = 0
implied = 0


func from_json_string(json: String) -> void:
var snake_case_enum: String = json.split(" ")[1]
if not _enum_dict.has(snake_case_enum.to_upper()):
push_warning("Unrecognized %s: %s" % [json.split(" ")[0], json.split(" ")[1]])
else:
target.set(name, Utils.enum_from_snake_case(_enum_dict, snake_case_enum))
var split := json.split(" ")
match split.size():
1:
target.set(name, implied)
2:
var snake_case_enum: String = json.split(" ")[1]
if not _enum_dict.has(snake_case_enum.to_upper()):
push_warning("Unrecognized %s: %s" % [json.split(" ")[0], json.split(" ")[1]])
else:
target.set(name, Utils.enum_from_snake_case(_enum_dict, snake_case_enum))


func to_json_string() -> String:
return "%s %s" % [name, Utils.enum_to_snake_case(_enum_dict, target.get(name))]
var result: String
if target.get(name) == implied:
result = name
else:
result = "%s %s" % [name, Utils.enum_to_snake_case(_enum_dict, target.get(name))]
return result


class OngoingRuleDefinition:
Expand All @@ -81,6 +154,10 @@ class OngoingRuleDefinition:

func default(new_default) -> void:
_rule_serializer.default = new_default


func implied(new_implied) -> void:
_rule_serializer.implied = new_implied

var _target: Object
var _rule_serializers := []
Expand All @@ -90,16 +167,20 @@ func _init(init_target: Object) -> void:
_target = init_target


func add_bool(name: String) -> OngoingRuleDefinition:
return add(BoolRuleSerializer.new(_target, name))
func add_bool(name: String, false_string: String = "") -> OngoingRuleDefinition:
return add(BoolRuleSerializer.new(_target, name, false_string))


func add_enum(name: String, dict: Dictionary) -> OngoingRuleDefinition:
return add(EnumRuleSerializer.new(_target, name, dict))


func add_int(name: String) -> OngoingRuleDefinition:
return add(IntRuleSerializer.new(_target, name))


func add_enum(name: String, dict: Dictionary) -> OngoingRuleDefinition:
return add(EnumRuleSerializer.new(_target, name, dict))
func add_string(name: String) -> OngoingRuleDefinition:
return add(StringRuleSerializer.new(_target, name))


func add(rule_serializer: RuleSerializer) -> OngoingRuleDefinition:
Expand Down
16 changes: 11 additions & 5 deletions project/src/main/puzzle/level/lose-condition-rules.gd
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@ var finish_on_lose := false
# by default, the player loses if they top out three times
var top_out := 3

var _rules_serializer: ArrayRulesSerializer

func _init() -> void:
_rules_serializer = ArrayRulesSerializer.new(self)
_rules_serializer.add_bool("finish_on_lose")
_rules_serializer.add_int("top_out").default(3)


func from_json_array(json: Array) -> void:
var rules := RuleParser.new(json)
if rules.has("finish_on_lose"): finish_on_lose = true
if rules.has("top_out"): top_out = rules.int_value()
_rules_serializer.from_json_array(json)


func to_json_array() -> Array:
return []
return _rules_serializer.to_json_array()


func is_default() -> bool:
return false
return _rules_serializer.is_default()
45 changes: 19 additions & 26 deletions project/src/main/puzzle/level/other-rules.gd
Original file line number Diff line number Diff line change
Expand Up @@ -39,36 +39,29 @@ var tile_set: int = PuzzleTileMap.TileSetType.DEFAULT
# 'true' for tutorial levels which are led by Turbo
var tutorial := false

var _rules_serializer: ArrayRulesSerializer

func _init() -> void:
_rules_serializer = ArrayRulesSerializer.new(self)
_rules_serializer.add_bool("after_tutorial")
_rules_serializer.add_bool("clear_on_finish", "no_clear_on_finish").default(true)
_rules_serializer.add_bool("enhance_combo_fx")
_rules_serializer.add_bool("non_interactive")
_rules_serializer.add_enum("suppress_piece_rotation", SuppressPieceRotation).implied(SuppressPieceRotation.ROTATION)
_rules_serializer.add_enum("suppress_piece_initial_rotation", SuppressPieceRotation).implied(SuppressPieceRotation.ROTATION)
_rules_serializer.add_bool("skip_intro")
_rules_serializer.add_string("start_level")
_rules_serializer.add_enum("tile_set", PuzzleTileMap.TileSetType)
_rules_serializer.add_bool("tutorial")


func from_json_array(json: Array) -> void:
var rules := RuleParser.new(json)
if rules.has("after_tutorial"): after_tutorial = true
if rules.has("enhance_combo_fx"): enhance_combo_fx = true
if rules.has("no_clear_on_finish"): clear_on_finish = false
if rules.has("non_interactive"): non_interactive = true
if rules.has("suppress_piece_rotation"):
match rules.string_value():
# rules.string_value() returns '1' if there are no parameters specified
"1", "rotation": suppress_piece_rotation = SuppressPieceRotation.ROTATION
"rotation_and_signals": suppress_piece_rotation = SuppressPieceRotation.ROTATION_AND_SIGNALS
_: push_warning("Unrecognized suppress_piece_rotation: %s" % [rules.string_value()])
if rules.has("suppress_piece_initial_rotation"):
match rules.string_value():
# rules.string_value() returns '1' if there are no parameters specified
"1", "rotation": suppress_piece_initial_rotation = SuppressPieceRotation.ROTATION
"rotation_and_signals": suppress_piece_initial_rotation = SuppressPieceRotation.ROTATION_AND_SIGNALS
_: push_warning("Unrecognized suppress_piece_initial_rotation: %s" % [rules.string_value()])
if rules.has("start_level"): start_level = rules.string_value()
if rules.has("tile_set"):
match rules.string_value():
"default": tile_set = PuzzleTileMap.TileSetType.DEFAULT
"veggie": tile_set = PuzzleTileMap.TileSetType.VEGGIE
_: push_warning("Unrecognized tile_set: %s" % [rules.string_value()])
if rules.has("tutorial"): tutorial = true
_rules_serializer.from_json_array(json)


func to_json_array() -> Array:
return []
return _rules_serializer.to_json_array()


func is_default() -> bool:
return false
return _rules_serializer.is_default()
20 changes: 20 additions & 0 deletions project/src/main/utils.gd
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,23 @@ static func enum_to_snake_case(

static func enum_from_snake_case(enum_dict: Dictionary, from: String, default: int = 0) -> int:
return enum_dict.get(from.to_upper(), default)


"""
Converts a string to a bool, treating values like 'False', and 'false' as false values.
This is a workaround for Godot #27529 (https://github.com/godotengine/godot/issues/27529). When converting a bool to a
String, the String is set to "True" and "False" for the appropriate boolean values. However, when converting a String
to a bool, the bool is set to true if the string is non-empty, regardless of the contents.
This code is adapted from Andrettin's suggested fix. Per his suggestion, the result is true if the String is "True",
"TRUE", "true" or "1", and false if the String is "False", "FALSE", "false" or "0". If the string is set to anything
else, then it is true if non-empty, and false otherwise.
"""
static func to_bool(s: String) -> bool:
var result: bool
match s:
"True", "TRUE", "true", "1": result = true
"False", "FALSE", "false", "0": result = false
_: result = false if s.empty() else true
return result
1 change: 1 addition & 0 deletions project/src/test/puzzle/level/test-blocks-during-rules.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var rules: BlocksDuringRules
func before_each() -> void:
rules = BlocksDuringRules.new()


func test_is_default() -> void:
assert_eq(rules.is_default(), true)
rules.pickup_type = BlocksDuringRules.PickupType.FLOAT_REGEN
Expand Down
18 changes: 9 additions & 9 deletions project/src/test/puzzle/level/test-level-settings.gd
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ func test_to_json_milestones_and_tiles() -> void:
assert_eq(settings.finish_condition.value, 180)
assert_eq(settings.success_condition.type, Milestone.LINES)
assert_eq(settings.success_condition.value, 100)
assert_eq(settings.tiles.bunches.keys(), ["start"])
assert_eq(settings.tiles.bunches["start"].block_tiles[Vector2(1, 2)], 3)
assert_eq(settings.tiles.bunches["start"].block_autotile_coords[Vector2(1, 2)], Vector2(4, 5))
# assert_eq(settings.tiles.bunches.keys(), ["start"])
# assert_eq(settings.tiles.bunches["start"].block_tiles[Vector2(1, 2)], 3)
# assert_eq(settings.tiles.bunches["start"].block_autotile_coords[Vector2(1, 2)], Vector2(4, 5))


func test_to_json_rules() -> void:
Expand All @@ -141,11 +141,11 @@ func test_to_json_rules() -> void:

assert_eq(settings.blocks_during.clear_on_top_out, true)
assert_eq(settings.combo_break.pieces, 3)
assert_eq(settings.input_replay.action_timings, {"25 +rotate_cw": true, "33 -rotate_cw": true})
assert_eq(settings.input_replay.action_timings.keys(), ["25 +rotate_cw", "33 -rotate_cw"])
assert_eq(settings.lose_condition.finish_on_lose, true)
assert_eq(settings.other.after_tutorial, true)
assert_eq(settings.piece_types.start_types, [PieceTypes.piece_j, PieceTypes.piece_l])
assert_eq(settings.rank.box_factor, 2.0)
assert_eq(settings.score.cake_points, 30)
assert_eq(settings.timers.timers, [{"interval": 5}])
assert_eq(settings.triggers.triggers.keys(), [LevelTrigger.AFTER_LINE_CLEARED])
# assert_eq(settings.piece_types.start_types, [PieceTypes.piece_j, PieceTypes.piece_l])
# assert_eq(settings.rank.box_factor, 2.0)
# assert_eq(settings.score.cake_points, 30)
# assert_eq(settings.timers.timers, [{"interval": 5}])
# assert_eq(settings.triggers.triggers.keys(), [LevelTrigger.AFTER_LINE_CLEARED])
31 changes: 31 additions & 0 deletions project/src/test/puzzle/level/test-lose-condition-rules.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
extends "res://addons/gut/test.gd"
"""
Tests for how the player loses. The player usually loses if they top out a certain number of times, but some levels
might have different rules.
"""

var rules: LoseConditionRules

func before_each() -> void:
rules = LoseConditionRules.new()


func test_is_default() -> void:
assert_eq(rules.is_default(), true)
rules.finish_on_lose = true
assert_eq(rules.is_default(), false)


func test_convert_settings_to_json_and_back() -> void:
rules.finish_on_lose = true
rules.top_out = 5
_convert_settings_to_json_and_back()

assert_eq(rules.finish_on_lose, true)
assert_eq(rules.top_out, 5)


func _convert_settings_to_json_and_back() -> void:
var json := rules.to_json_array()
rules = LoseConditionRules.new()
rules.from_json_array(json)
Loading

0 comments on commit 7886da4

Please sign in to comment.