diff --git a/GridNumbers.xcf b/GridNumbers.xcf new file mode 100644 index 0000000..6092269 Binary files /dev/null and b/GridNumbers.xcf differ diff --git a/addons/godot-git-plugin/git_api.gdnlib b/addons/godot-git-plugin/git_api.gdnlib index 0918c1c..18a34ed 100644 --- a/addons/godot-git-plugin/git_api.gdnlib +++ b/addons/godot-git-plugin/git_api.gdnlib @@ -1,18 +1,18 @@ -[general] - -singleton=true -load_once=true -symbol_prefix="godot_" -reloadable=false - -[entry] - -OSX.64="res://addons/godot-git-plugin/osx/release/libgitapi.dylib" -Windows.64="res://addons/godot-git-plugin/win64/release/libgitapi.dll" -X11.64="res://addons/godot-git-plugin/x11/release/libgitapi.so" - -[dependencies] - -OSX.64=[ ] -Windows.64=[ ] -X11.64=[ ] +[general] + +singleton=true +load_once=true +symbol_prefix="godot_" +reloadable=false + +[entry] + +OSX.64="res://addons/godot-git-plugin/osx/release/libgitapi.dylib" +Windows.64="res://addons/godot-git-plugin/win64/release/libgitapi.dll" +X11.64="res://addons/godot-git-plugin/x11/release/libgitapi.so" + +[dependencies] + +OSX.64=[ ] +Windows.64=[ ] +X11.64=[ ] diff --git a/default_env.tres b/default_env.tres index 98f26a7..20207a4 100644 --- a/default_env.tres +++ b/default_env.tres @@ -1,5 +1,7 @@ [gd_resource type="Environment" load_steps=2 format=2] + [sub_resource type="ProceduralSky" id=1] + [resource] background_mode = 2 background_sky = SubResource( 1 ) diff --git a/project.godot b/project.godot index e969bcd..398b1d2 100644 --- a/project.godot +++ b/project.godot @@ -9,20 +9,39 @@ config_version=4 _global_script_classes=[ { +"base": "Node2D", +"class": "GameScene", +"language": "GDScript", +"path": "res://scripts/GameScene.gd" +}, { +"base": "Reference", +"class": "GameState", +"language": "GDScript", +"path": "res://scripts/GameState.gd" +}, { "base": "", "class": "GitAPI", "language": "NativeScript", "path": "res://addons/godot-git-plugin/git_api.gdns" } ] _global_script_class_icons={ +"GameScene": "", +"GameState": "", "GitAPI": "" } [application] config/name="LoopyTest" +run/main_scene="res://scenes/GameScene.tscn" config/icon="res://icon.png" +[debug] + +gdscript/warnings/treat_warnings_as_errors=true +gdscript/warnings/return_value_discarded=false +gdscript/warnings/integer_division=false + [gdnative] singletons=[ "res://addons/godot-git-plugin/git_api.gdnlib" ] diff --git a/scenes/Cell.tscn b/scenes/Cell.tscn new file mode 100644 index 0000000..0ec9248 --- /dev/null +++ b/scenes/Cell.tscn @@ -0,0 +1,8 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://scripts/Cell.gd" type="Script" id=1] + +[node name="Cell" type="Polygon2D"] +position = Vector2( 329, 258 ) +polygon = PoolVector2Array( 35.3553, 35.3553, -35.3553, 35.3553, -35.3553, -35.3553, 35.3553, -35.3553 ) +script = ExtResource( 1 ) diff --git a/scenes/GameScene.tscn b/scenes/GameScene.tscn new file mode 100644 index 0000000..6868f31 --- /dev/null +++ b/scenes/GameScene.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://scripts/Line.gd" type="Script" id=1] +[ext_resource path="res://scripts/GameScene.gd" type="Script" id=2] +[ext_resource path="res://scripts/PlayerController.gd" type="Script" id=3] + + +[node name="GameBoard" type="Node2D"] +script = ExtResource( 2 ) + +[node name="Cell" parent="." instance_placeholder="res://Cell.tscn"] +color = Color( 0.978975, 0.256011, 0.734578, 1 ) + +[node name="Line" type="Line2D" parent="."] +position = Vector2( 55.0797, -23.3827 ) +points = PoolVector2Array( 236.347, 320.204, 236.382, 243.676 ) +default_color = Color( 0.537255, 0.537255, 0.537255, 1 ) +script = ExtResource( 1 ) + +[node name="PlayerController" type="Node" parent="."] +script = ExtResource( 3 ) diff --git a/scenes/Line.tscn b/scenes/Line.tscn new file mode 100644 index 0000000..06e4365 --- /dev/null +++ b/scenes/Line.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://scripts/Line.gd" type="Script" id=1] + +[sub_resource type="CapsuleShape2D" id=1] +height = 10.2288 + +[node name="Line" type="Line2D"] +points = PoolVector2Array( 0, 0, 1, 1 ) +default_color = Color( 0.537255, 0.537255, 0.537255, 1 ) +script = ExtResource( 1 ) + +[node name="Area2D" type="Area2D" parent="."] + +[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] +shape = SubResource( 1 ) +[connection signal="mouse_entered" from="Area2D" to="." method="_on_Area2D_mouse_entered"] +[connection signal="mouse_exited" from="Area2D" to="." method="_on_Area2D_mouse_exited"] diff --git a/scenes/Scene.tscn b/scenes/Scene.tscn new file mode 100644 index 0000000..32c41d7 --- /dev/null +++ b/scenes/Scene.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://scripts/PlayerController.gd" type="Script" id=3] +[ext_resource path="res://scripts/GameScene.gd" type="Script" id=4] + +[node name="Node2D" type="Node2D"] +script = ExtResource( 4 ) + +[node name="PlayerController" type="Node" parent="."] +script = ExtResource( 3 ) diff --git a/scripts/Cell.gd b/scripts/Cell.gd new file mode 100644 index 0000000..94465de --- /dev/null +++ b/scripts/Cell.gd @@ -0,0 +1,37 @@ +extends Polygon2D + +export var sides: int = 4 +export var size: float = 50 + +var idx: int +var accessor: GameScene.CellAccessor + +var moveSpeed: float = rand_range(0.5,1.5) + +func _ready() -> void: + init() + +func init() -> void: + var points: PoolVector2Array = [] + + for side in range(sides): + var angle: float = ((side / float(sides)) + (0.5 / sides) ) * (2 * PI) + points.append(Vector2(cos(angle),sin(angle)) * size) + + polygon = points + +# color = Color(randf(),randf(),randf()) + accessor = get_parent().get_cell(idx) + +func _process(delta): + var target: Vector2 = accessor.get_pos() + Vector2(size,size) + position = position.linear_interpolate(target,delta*moveSpeed) + + var owner: int = accessor.get_owner() + match owner: + 0: + color = Color.white + 1: + color = Color.pink + 2: + color = Color.skyblue diff --git a/scripts/GameScene.gd b/scripts/GameScene.gd new file mode 100644 index 0000000..efa40ce --- /dev/null +++ b/scripts/GameScene.gd @@ -0,0 +1,238 @@ +extends Node2D + +export var boardSize: Vector2 = Vector2(10,7) + +class_name GameScene + +var reality: GameState +var simulate: GameState + +func _ready() -> void: + reality = GameState.new() + reality.init_arrays(boardSize) + + var cellScene: PackedScene = load("res://scenes/Cell.tscn") + var lineScene: PackedScene = load("res://scenes/Line.tscn") + for cellIdx in range(reality.get_cell_count()): + var cell: Node = cellScene.instance() + cell.idx = cellIdx + add_child(cell) + for lineIdx in range(reality.get_line_count()): + var line: Node = lineScene.instance() + line.idx = lineIdx + add_child(line) + line.connect("line_clicked", self, "_line_clicked") + + print("Lines at 0,0: %s" % [get_lines_by_grid(Vector2(0,0))]) + print("Lines at 0,1: %s" % [get_lines_by_grid(Vector2(0,1))]) + + +func _line_clicked(lineIdx: int): + var line: LineAccessor = get_line(lineIdx) + line.set_owner(1) + var surrounds: int = test_line_surrounds(lineIdx) + print("Line %d clicked! Surrounded: %d" % [lineIdx, surrounds]) + # +ve: down and left + # -ve: up and right + var cell: CellAccessor = null + if surrounds > 0: + if line.get_horizontal(): + cell = get_cell_by_grid(line.get_grid1() + Vector2(0,1)) + else: + cell = get_cell_by_grid(line.get_grid1()) + elif surrounds < 0: + if line.get_horizontal(): + cell = get_cell_by_grid(line.get_grid1()) + else: + cell = get_cell_by_grid(line.get_grid1() + Vector2(1,0)) + if cell != null: + flood_owner(cell, 1) + +func flood_owner(cell: CellAccessor, owner: int): + if cell.get_owner() != 0: + return + cell.set_owner(owner) + if get_line_by_grid(cell.get_grid() - Vector2(0,1), true).get_owner() == 0: + flood_owner(get_cell_by_grid(cell.get_grid() - Vector2(0,1)), owner) + if get_line_by_grid(cell.get_grid(), true).get_owner() == 0: + flood_owner(get_cell_by_grid(cell.get_grid() + Vector2(0,1)), owner) + if get_line_by_grid(cell.get_grid() - Vector2(1,0), false).get_owner() == 0: + flood_owner(get_cell_by_grid(cell.get_grid() - Vector2(1,0)), owner) + if get_line_by_grid(cell.get_grid(), false).get_owner() == 0: + flood_owner(get_cell_by_grid(cell.get_grid() + Vector2(1,0)), owner) + +func test_line_surrounds(startIdx: int) -> int: + var startLine: LineAccessor = get_line(startIdx) + var grid0: Vector2 = startLine.get_grid0() + var grid1: Vector2 = startLine.get_grid1() + + var openSet: Array = [grid0] # points yet to visit + var closedSet: Array = [] # points visited already + + while openSet.size() > 0: + var point: Vector2 = openSet.pop_front() + closedSet.push_back(point) + + var lines: Array = get_lines_by_grid(point) + for line in lines: + if line.get_owner() == 0: + continue # don't follow unowned lines + if line.idx == startIdx: + continue # don't follow the start line + var otherSide: Vector2 = line.get_grid_other_end(point) + if otherSide == grid1: + closedSet.append(otherSide) + print("Loop found: %s" % [closedSet]) + return calulate_winding(closedSet) # we made it! + if otherSide in closedSet: + continue # been there done that + elif otherSide in openSet: + continue # we already plan to go there + else: + openSet.push_back(otherSide) + + return 0 + +func calulate_winding(points: Array) -> int: + # Sum over the edges, (x2 − x1)(y2 + y1). + # If the result is positive the curve is clockwise, if it's negative the curve is counter-clockwise. + # (The result is twice the enclosed area, with a +/- convention.) + + var ret: int = 0 + for idx in range(points.size()): + var point1: Vector2 = points[idx-1] + var point2: Vector2 = points[idx] + ret += int(point2.x - point1.x) * int(point2.y + point1.y) + + return ret/2 + +func get_cell(idx: int) -> CellAccessor: + var ret: CellAccessor = CellAccessor.new() + ret.gameScene = self + ret.idx = idx + return ret + +func get_cell_idx_by_grid(pos: Vector2) -> int: + return int((pos.y * boardSize.x) + pos.x) + +func get_cell_by_grid(pos: Vector2) -> CellAccessor: + return get_cell(get_cell_idx_by_grid(pos)) + +func get_line(idx: int) -> LineAccessor: + var ret: LineAccessor = LineAccessor.new() + ret.idx = idx + ret.gameScene = self + return ret + +func get_line_by_grid(pos: Vector2, horizontal: bool) -> LineAccessor: + var idx: int + if horizontal: + idx = get_cell_idx_by_grid(pos) + else: + var cellsLastRow: int = int ((boardSize.x * boardSize.y) - boardSize.x) + idx = int(cellsLastRow + pos.y + (pos.x * boardSize.y)) + return get_line(idx) + +func get_lines_by_grid(grid: Vector2) -> Array: + var ret: Array = [] + if grid.y >= 0: + ret.append(get_line_by_grid(grid, false)) + if grid.x >= 0: + ret.append(get_line_by_grid(grid, true)) + ret.append(get_line_by_grid(grid + Vector2(0,1),false)) + ret.append(get_line_by_grid(grid + Vector2(1,0),true)) + return ret + +class CellAccessor: + var gameScene: GameScene + var idx: int + + func get_state(real: bool = true): # NOTE: duplicated + if real: + return gameScene.reality + else: + return gameScene.simulate + + func get_owner(real: bool = true) -> int: + return get_state(real).cellOwners[idx] + + func set_owner(owner: int, real: bool = true): + get_state(real).cellOwners[idx] = owner + + func get_grid() -> Vector2: + var col: int = idx % int(gameScene.boardSize.x) + var row: int = idx / int(gameScene.boardSize.x) + return Vector2(col,row) + + func get_pos() -> Vector2: + return get_grid() * 75 + +class LineAccessor: + var gameScene: GameScene + var idx: int + + func get_state(real: bool = true): # NOTE: duplicated + if real: + return gameScene.reality + else: + return gameScene.simulate + + func get_owner(real: bool = true) -> int: + return get_state(real).lineOwners[idx] + + func set_owner(owner: int, real: bool = true): + get_state(real).lineOwners[idx] = owner + + func get_cellIdx() -> int: + var cellsLastRow: int = int ((gameScene.boardSize.x * gameScene.boardSize.y) - gameScene.boardSize.x) + if idx < cellsLastRow: + return idx + else: + var ln: int = idx - cellsLastRow + var row: int = ln % int(gameScene.boardSize.y) + var col: int = ln / int(gameScene.boardSize.y) + return (row * int(gameScene.boardSize.x)) + col + + func get_horizontal() -> bool: + var cellsLastRow: int = int ((gameScene.boardSize.x * gameScene.boardSize.y) - gameScene.boardSize.x) + return idx <= cellsLastRow + + func get_point0() -> Vector2: + return (get_grid0() * 75) + Vector2(85,85) + + func get_point1() -> Vector2: + return (get_grid1() * 75) + Vector2(85,85) + + func get_grid0() -> Vector2: + var grid1: Vector2 = get_grid1() + if get_horizontal(): + return grid1 - Vector2(1,0) + else: + return grid1 - Vector2(0,1) + + func get_grid1() -> Vector2: + # NOTE: duplicated + var cellIdx: int = get_cellIdx() + var col: int = cellIdx % int(gameScene.boardSize.x) + var row: int = cellIdx / int(gameScene.boardSize.x) + return Vector2(col,row) + + func get_grid_other_end(grid: Vector2): + if grid == get_grid0(): + return get_grid1() + elif grid == get_grid1(): + return get_grid0() + else: + assert(false) + + func get_neighbours() -> Array: + var ret: Array = [] + for line in gameScene.get_lines_by_grid(get_grid0()): + ret.append(line) + for line in gameScene.get_lines_by_grid(get_grid1()): + ret.append(line) + ret.remove(ret.find(self)) + return ret + + func _to_string() -> String: + return "Line %d" % [idx] diff --git a/scripts/GameState.gd b/scripts/GameState.gd new file mode 100644 index 0000000..25422bf --- /dev/null +++ b/scripts/GameState.gd @@ -0,0 +1,32 @@ +extends Reference + +class_name GameState + +# var parent: GameState # possibly reference the previous state and only store changed cells + +# cell state info +var cellOwners: Array + +# line state info +var lineOwners: Array + +func init_arrays(size: Vector2): + var cells: int = int(size.x * size.y) + # A grid graph G_(m,n) has mn nodes and (m-1)n+(n-1)m=2mn-m-n edges + var lines: int = int((2 * size.x * size.y) - size.x - size.y) + for _cell in range(cells): + cellOwners.append(0) + for _line in range(lines): + lineOwners.append(0) + +func clone() -> GameState: + var ret = .new() + ret.cellOwners = cellOwners.duplicate() + ret.lineOwners = lineOwners.duplicate() + return ret; + +func get_cell_count(): + return cellOwners.size() + +func get_line_count(): + return lineOwners.size() diff --git a/scripts/Line.gd b/scripts/Line.gd new file mode 100644 index 0000000..811f370 --- /dev/null +++ b/scripts/Line.gd @@ -0,0 +1,47 @@ +extends Line2D + +var idx: int +var accessor: GameScene.LineAccessor + +var hovered: bool + +var moveSpeed: float = rand_range(0.5,1.5) + +signal line_clicked(id) + +func _ready() -> void: + accessor = get_parent().get_line(idx) + +func _process(delta: float) -> void: + var p0: Vector2 = accessor.get_point0() + var p1: Vector2 = accessor.get_point1() + points[0] = points[0].linear_interpolate(p0,delta*moveSpeed) + points[1] = points[1].linear_interpolate(p1,delta*moveSpeed) + + if $Area2D/CollisionShape2D != null: + $Area2D/CollisionShape2D.rotation = p0.angle_to(p1) + (0.5 * PI) + $Area2D/CollisionShape2D.position = p0.linear_interpolate(p1,0.5) + $Area2D/CollisionShape2D.shape.height = p0.distance_to(p1) + + var owner: int = accessor.get_owner() + match owner: + 0: + default_color = Color.gray + 1: + default_color = Color.red + 2: + default_color = Color.blue + + if hovered: + default_color = default_color.lightened(0.2) + +func _input(event: InputEvent) -> void: + if event is InputEventMouseButton: + if event.button_index == BUTTON_LEFT and event.pressed and hovered: + emit_signal("line_clicked", idx) + +func _on_Area2D_mouse_entered() -> void: + hovered = true + +func _on_Area2D_mouse_exited() -> void: + hovered = false diff --git a/scripts/PlayerController.gd b/scripts/PlayerController.gd new file mode 100644 index 0000000..291ae5d --- /dev/null +++ b/scripts/PlayerController.gd @@ -0,0 +1,16 @@ +extends Node + + +# Declare member variables here. Examples: +# var a: int = 2 +# var b: String = "text" + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +#func _process(delta: float) -> void: +# pass