10 Commits

46 changed files with 851 additions and 312 deletions

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@
*.sln *.sln
*.sln.* *.sln.*
.DS_Store .DS_Store
**/.DS_Store
Folder.DotSettings.user

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Joshua Higgins
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1 +1,9 @@
# Connect4 Moderator - Observer # Connect4 Moderator - Observer
The front end for the [server](https://github.com/joshuafhiggins/connect4-moderator-server) made in [Godot](https://godotengine.org/), an open-soruce game engine.
# Downloads
See [releases](https://github.com/joshuafhiggins/connect4-moderator-observer/releases)
# For Future Maintainers
This was made in Godot 4.5. Due to the use of C# for a lot of the scripting, we are unable to export for the web but [progress is being made](https://github.com/godotengine/godot/pull/106125). An icon in Icon Composer was made for macOS builds but can't be used till Godot 4.6. Currently, there is also a bug in Godot's Websocket implementation that causes random disconnects, options are submitting upstream fixes and debugging or wrapping a third party library to use. It would also be nice to rework the bracket screen to show the bracket and be more thematic with the other pixel art pieces. Some scripts are leftovers/incomplete implementations of having this program act as a client, which would be helpful to finish.

BIN
assets/.DS_Store vendored

Binary file not shown.

1
assets/music/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
jazz_music.mp3 filter=lfs diff=lfs merge=lfs -text

BIN
assets/music/jazz_music.mp3 LFS Normal file

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="mp3"
type="AudioStreamMP3"
uid="uid://csy7ltflvsjq5"
path="res://.godot/imported/jazz_music.mp3-51c0488dbe42064eda25eafa56d387ed.mp3str"
[deps]
source_file="res://assets/music/jazz_music.mp3"
dest_files=["res://.godot/imported/jazz_music.mp3-51c0488dbe42064eda25eafa56d387ed.mp3str"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://dauf0pi1pkd3x"
path="res://.godot/imported/chip_collide_1.ogg-406dd38ea9c3d5652eebd8457cb16d54.oggvorbisstr"
[deps]
source_file="res://assets/sfx/chip_collide_1.ogg"
dest_files=["res://.godot/imported/chip_collide_1.ogg-406dd38ea9c3d5652eebd8457cb16d54.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://b6b7b7sc038n7"
path="res://.godot/imported/chip_collide_2.ogg-f3e4af1639a40a7e5696a0d76240e083.oggvorbisstr"
[deps]
source_file="res://assets/sfx/chip_collide_2.ogg"
dest_files=["res://.godot/imported/chip_collide_2.ogg-f3e4af1639a40a7e5696a0d76240e083.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://c6a4wqoopu53j"
path="res://.godot/imported/chip_collide_3.ogg-8dacdc0da1447d7f98373dc814c23342.oggvorbisstr"
[deps]
source_file="res://assets/sfx/chip_collide_3.ogg"
dest_files=["res://.godot/imported/chip_collide_3.ogg-8dacdc0da1447d7f98373dc814c23342.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://5liedrachob2"
path="res://.godot/imported/chip_collide_4.ogg-a636adca2293a212ad4dd420b3445fd5.oggvorbisstr"
[deps]
source_file="res://assets/sfx/chip_collide_4.ogg"
dest_files=["res://.godot/imported/chip_collide_4.ogg-a636adca2293a212ad4dd420b3445fd5.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://dsyovynhhbmw8"
path="res://.godot/imported/chip_collide_5.ogg-43d68269cdbbab204cb581d3f2f55acc.oggvorbisstr"
[deps]
source_file="res://assets/sfx/chip_collide_5.ogg"
dest_files=["res://.godot/imported/chip_collide_5.ogg-43d68269cdbbab204cb581d3f2f55acc.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://c0a5rl5q04noq"
path="res://.godot/imported/chip_collide_6.ogg-277685742efbfc8894a8af489e0a5d40.oggvorbisstr"
[deps]
source_file="res://assets/sfx/chip_collide_6.ogg"
dest_files=["res://.godot/imported/chip_collide_6.ogg-277685742efbfc8894a8af489e0a5d40.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://b6d73wiiqxles"
path="res://.godot/imported/chip_collide_7.ogg-7f61ae7f863bd72768f0889e7ed11946.oggvorbisstr"
[deps]
source_file="res://assets/sfx/chip_collide_7.ogg"
dest_files=["res://.godot/imported/chip_collide_7.ogg-7f61ae7f863bd72768f0889e7ed11946.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

BIN
assets/sfx/game_end.ogg Normal file

Binary file not shown.

View File

@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://crxjuk1vyq331"
path="res://.godot/imported/game_end.ogg-8ceab2b7181c15a955c6f20334947ba7.oggvorbisstr"
[deps]
source_file="res://assets/sfx/game_end.ogg"
dest_files=["res://.godot/imported/game_end.ogg-8ceab2b7181c15a955c6f20334947ba7.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

BIN
assets/sprites/rpi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://uritd4ygetrk"
path="res://.godot/imported/rpi.png-0f0faa9ccfa1d0b656d9c381bb4a7a6d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/sprites/rpi.png"
dest_files=["res://.godot/imported/rpi.png-0f0faa9ccfa1d0b656d9c381bb4a7a6d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

6
assets/theme.tres Normal file
View File

@@ -0,0 +1,6 @@
[gd_resource type="Theme" load_steps=2 format=3 uid="uid://bbgxacei1vwba"]
[ext_resource type="FontFile" uid="uid://c3jmev24lo6ci" path="res://assets/fonts/PixelOperator8.ttf" id="1_5x3i2"]
[resource]
default_font = ExtResource("1_5x3i2")

View File

@@ -11,7 +11,7 @@ config_version=5
[application] [application]
config/name="Connect4 Observer" config/name="Connect4 Observer"
config/version="0.1.3" config/version="1.0.0"
run/main_scene="uid://dcx5nvs0pa7me" run/main_scene="uid://dcx5nvs0pa7me"
config/features=PackedStringArray("4.5", "C#", "Forward Plus") config/features=PackedStringArray("4.5", "C#", "Forward Plus")
boot_splash/image="uid://dd7lvnidxr5ss" boot_splash/image="uid://dd7lvnidxr5ss"
@@ -20,6 +20,7 @@ config/icon="uid://dd7lvnidxr5ss"
[autoload] [autoload]
Connection="*res://scripts/Connection.cs" Connection="*res://scripts/Connection.cs"
BackgroundMusic="*res://scripts/background_music.gd"
[display] [display]
@@ -27,6 +28,7 @@ window/size/viewport_width=1280
window/size/viewport_height=720 window/size/viewport_height=720
window/stretch/mode="canvas_items" window/stretch/mode="canvas_items"
window/stretch/aspect="expand" window/stretch/aspect="expand"
window/vsync/vsync_mode=2
[dotnet] [dotnet]

View File

@@ -1,7 +1,9 @@
[gd_scene load_steps=15 format=3 uid="uid://m542qwlp7hl7"] [gd_scene load_steps=17 format=3 uid="uid://m542qwlp7hl7"]
[ext_resource type="Script" uid="uid://dg5jt0o0r0v3r" path="res://scripts/BoardScreen.cs" id="1_b3w8x"] [ext_resource type="Script" uid="uid://dg5jt0o0r0v3r" path="res://scripts/BoardScreen.cs" id="1_b3w8x"]
[ext_resource type="AudioStream" uid="uid://crxjuk1vyq331" path="res://assets/sfx/game_end.ogg" id="2_kseed"]
[ext_resource type="Texture2D" uid="uid://dlx02qat7j6lf" path="res://assets/sprites/AssetTileset.png" id="3_1tlhv"] [ext_resource type="Texture2D" uid="uid://dlx02qat7j6lf" path="res://assets/sprites/AssetTileset.png" id="3_1tlhv"]
[ext_resource type="Theme" uid="uid://bbgxacei1vwba" path="res://assets/theme.tres" id="3_3louw"]
[ext_resource type="FontFile" uid="uid://c3jmev24lo6ci" path="res://assets/fonts/PixelOperator8.ttf" id="3_rjcmr"] [ext_resource type="FontFile" uid="uid://c3jmev24lo6ci" path="res://assets/fonts/PixelOperator8.ttf" id="3_rjcmr"]
[ext_resource type="Texture2D" uid="uid://ckmfi0cjgxgyk" path="res://assets/sprites/RedChip.png" id="4_1hrcj"] [ext_resource type="Texture2D" uid="uid://ckmfi0cjgxgyk" path="res://assets/sprites/RedChip.png" id="4_1hrcj"]
[ext_resource type="Texture2D" uid="uid://qy30emdgrk7o" path="res://assets/sprites/YellowChip.png" id="5_i2o8i"] [ext_resource type="Texture2D" uid="uid://qy30emdgrk7o" path="res://assets/sprites/YellowChip.png" id="5_i2o8i"]
@@ -34,6 +36,8 @@ region = Rect2(112, 32, 16, 16)
[node name="BoardScreen" type="Node2D"] [node name="BoardScreen" type="Node2D"]
script = ExtResource("1_b3w8x") script = ExtResource("1_b3w8x")
endingSfx = ExtResource("2_kseed")
theme = ExtResource("3_3louw")
[node name="Floor Collider" type="StaticBody2D" parent="."] [node name="Floor Collider" type="StaticBody2D" parent="."]
position = Vector2(0, 200) position = Vector2(0, 200)

View File

@@ -1,10 +1,22 @@
[gd_scene load_steps=5 format=3 uid="uid://rl33x81cxlh0"] [gd_scene load_steps=8 format=3 uid="uid://rl33x81cxlh0"]
[ext_resource type="Theme" uid="uid://bbgxacei1vwba" path="res://assets/theme.tres" id="1_as653"]
[ext_resource type="Script" uid="uid://dm25u0a2lqk2x" path="res://scripts/BracketScene.cs" id="1_dvj3m"] [ext_resource type="Script" uid="uid://dm25u0a2lqk2x" path="res://scripts/BracketScene.cs" id="1_dvj3m"]
[ext_resource type="Texture2D" uid="uid://da13ksuf4vkqe" path="res://assets/sprites/observe.png" id="2_mbqc8"] [ext_resource type="Texture2D" uid="uid://da13ksuf4vkqe" path="res://assets/sprites/observe.png" id="2_mbqc8"]
[ext_resource type="Texture2D" uid="uid://stk7umv2ppss" path="res://assets/sprites/cancel.png" id="3_as653"] [ext_resource type="Texture2D" uid="uid://stk7umv2ppss" path="res://assets/sprites/cancel.png" id="3_as653"]
[ext_resource type="Script" uid="uid://1y72woiynf31" path="res://scripts/AdminControls.cs" id="4_mbqc8"] [ext_resource type="Script" uid="uid://1y72woiynf31" path="res://scripts/AdminControls.cs" id="4_mbqc8"]
[sub_resource type="Gradient" id="Gradient_wu84c"]
colors = PackedColorArray(0, 0.07058824, 0.101960786, 1, 0.39215687, 0.39215687, 0.39215687, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_as653"]
gradient = SubResource("Gradient_wu84c")
width = 1024
height = 1024
fill_from = Vector2(0.5, 1)
fill_to = Vector2(0.5, 0)
metadata/_snap_enabled = true
[node name="BracketView" type="Control" node_paths=PackedStringArray("Players", "Matches")] [node name="BracketView" type="Control" node_paths=PackedStringArray("Players", "Matches")]
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
@@ -12,12 +24,24 @@ anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
theme = ExtResource("1_as653")
script = ExtResource("1_dvj3m") script = ExtResource("1_dvj3m")
Players = NodePath("HBoxContainer/PlayerList") Players = NodePath("HBoxContainer/PlayerList")
Matches = NodePath("HBoxContainer/MatchList") Matches = NodePath("HBoxContainer/MatchList")
WatchButton = ExtResource("2_mbqc8") WatchButton = ExtResource("2_mbqc8")
TerminateKickButton = ExtResource("3_as653") TerminateKickButton = ExtResource("3_as653")
[node name="Background" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -152.0
offset_bottom = 152.0
grow_horizontal = 2
grow_vertical = 2
texture = SubResource("GradientTexture2D_as653")
[node name="ColorRect" type="ColorRect" parent="."] [node name="ColorRect" type="ColorRect" parent="."]
custom_minimum_size = Vector2(0, 36) custom_minimum_size = Vector2(0, 36)
layout_mode = 1 layout_mode = 1
@@ -26,7 +50,7 @@ anchor_right = 1.0
grow_horizontal = 2 grow_horizontal = 2
color = Color(0.13319641, 0.13319641, 0.13319638, 1) color = Color(0.13319641, 0.13319641, 0.13319638, 1)
[node name="AdminControls" type="HBoxContainer" parent="ColorRect" node_paths=PackedStringArray("BecomeAdmin", "StartTournament", "Label", "Timeout")] [node name="AdminControls" type="HBoxContainer" parent="ColorRect" node_paths=PackedStringArray("BecomeAdmin", "StartTournament", "CancelTournament", "Label", "Timeout")]
custom_minimum_size = Vector2(0, 36) custom_minimum_size = Vector2(0, 36)
layout_mode = 1 layout_mode = 1
anchors_preset = 10 anchors_preset = 10
@@ -36,6 +60,7 @@ grow_horizontal = 2
script = ExtResource("4_mbqc8") script = ExtResource("4_mbqc8")
BecomeAdmin = NodePath("BecomeAdmin") BecomeAdmin = NodePath("BecomeAdmin")
StartTournament = NodePath("StartTournament") StartTournament = NodePath("StartTournament")
CancelTournament = NodePath("CancelTournament")
Label = NodePath("Label") Label = NodePath("Label")
Timeout = NodePath("HSlider") Timeout = NodePath("HSlider")
@@ -47,6 +72,10 @@ text = "Become Admin"
layout_mode = 2 layout_mode = 2
text = "Start Tournament" text = "Start Tournament"
[node name="CancelTournament" type="Button" parent="ColorRect/AdminControls"]
layout_mode = 2
text = "Cancel Tournament"
[node name="Label" type="Label" parent="ColorRect/AdminControls"] [node name="Label" type="Label" parent="ColorRect/AdminControls"]
layout_mode = 2 layout_mode = 2
text = "Wait To Move: 5.0s " text = "Wait To Move: 5.0s "

View File

@@ -1,10 +1,33 @@
[gd_scene load_steps=3 format=3 uid="uid://cr8fi0e4r88s8"] [gd_scene load_steps=5 format=3 uid="uid://cr8fi0e4r88s8"]
[ext_resource type="PackedScene" uid="uid://cct663hb47yka" path="res://scenes/create_join_room.tscn" id="1_yqjtg"] [ext_resource type="PackedScene" uid="uid://cct663hb47yka" path="res://scenes/create_join_room.tscn" id="1_yqjtg"]
[ext_resource type="PackedScene" uid="uid://m542qwlp7hl7" path="res://scenes/board_screen.tscn" id="2_lnu2h"] [ext_resource type="PackedScene" uid="uid://m542qwlp7hl7" path="res://scenes/board_screen.tscn" id="2_lnu2h"]
[sub_resource type="Gradient" id="Gradient_wu84c"]
colors = PackedColorArray(0, 0.07058824, 0.101960786, 1, 0.39215687, 0.39215687, 0.39215687, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_yqjtg"]
gradient = SubResource("Gradient_wu84c")
width = 1024
height = 1024
fill_from = Vector2(0.5, 1)
fill_to = Vector2(0.5, 0)
metadata/_snap_enabled = true
[node name="Game" type="Node2D"] [node name="Game" type="Node2D"]
[node name="Background" type="TextureRect" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -658.0
offset_top = -547.0
offset_right = 668.0
offset_bottom = 657.0
grow_horizontal = 2
grow_vertical = 2
texture = SubResource("GradientTexture2D_yqjtg")
[node name="GameManager" type="Node" parent="."] [node name="GameManager" type="Node" parent="."]
[node name="Camera2D" type="Camera2D" parent="."] [node name="Camera2D" type="Camera2D" parent="."]

View File

@@ -1,7 +1,20 @@
[gd_scene load_steps=3 format=3 uid="uid://dcx5nvs0pa7me"] [gd_scene load_steps=7 format=3 uid="uid://dcx5nvs0pa7me"]
[ext_resource type="Script" uid="uid://bk22f71oximjk" path="res://scripts/AddressUI.cs" id="1_l6cm7"] [ext_resource type="Script" uid="uid://bk22f71oximjk" path="res://scripts/AddressUI.cs" id="1_l6cm7"]
[ext_resource type="Theme" uid="uid://bbgxacei1vwba" path="res://assets/theme.tres" id="1_wu84c"]
[ext_resource type="Script" uid="uid://cpjbiqn26khck" path="res://scripts/ConnectButtonUI.cs" id="2_ekxnf"] [ext_resource type="Script" uid="uid://cpjbiqn26khck" path="res://scripts/ConnectButtonUI.cs" id="2_ekxnf"]
[ext_resource type="Texture2D" uid="uid://uritd4ygetrk" path="res://assets/sprites/rpi.png" id="3_bqqt6"]
[sub_resource type="Gradient" id="Gradient_wu84c"]
colors = PackedColorArray(0, 0.07058824, 0.101960786, 1, 0.39215687, 0.39215687, 0.39215687, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_yqjtg"]
gradient = SubResource("Gradient_wu84c")
width = 1024
height = 1024
fill_from = Vector2(0.5, 1)
fill_to = Vector2(0.5, 0)
metadata/_snap_enabled = true
[node name="Control" type="Control"] [node name="Control" type="Control"]
layout_mode = 3 layout_mode = 3
@@ -11,6 +24,63 @@ anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
[node name="Background" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = SubResource("GradientTexture2D_yqjtg")
[node name="RPI Minds and Machines" type="Label" parent="."]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -193.5
offset_top = 128.0
offset_right = 193.5
offset_bottom = 152.0
grow_horizontal = 2
theme = ExtResource("1_wu84c")
theme_type_variation = &"HeaderMedium"
text = "RPI Minds & Machines"
horizontal_alignment = 1
vertical_alignment = 1
[node name="Connect" type="Label" parent="."]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -100.0
offset_top = 200.0
offset_right = 68.00122
offset_bottom = 229.0
grow_horizontal = 2
theme = ExtResource("1_wu84c")
theme_type_variation = &"HeaderLarge"
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 16
text = "Connect"
[node name="4" type="Label" parent="."]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = 78.0
offset_top = 200.0
offset_right = 118.00116
offset_bottom = 229.0
grow_horizontal = 2
theme = ExtResource("1_wu84c")
theme_type_variation = &"HeaderLarge"
theme_override_colors/font_color = Color(1, 0, 0, 1)
theme_override_constants/outline_size = 16
text = "4"
[node name="Address" type="TextEdit" parent="."] [node name="Address" type="TextEdit" parent="."]
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
@@ -24,10 +94,13 @@ offset_right = 250.0
offset_bottom = 20.0 offset_bottom = 20.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
theme = ExtResource("1_wu84c")
placeholder_text = "Server Address" placeholder_text = "Server Address"
emoji_menu_enabled = false
scroll_smooth = true
script = ExtResource("1_l6cm7") script = ExtResource("1_l6cm7")
[node name="Button" type="Button" parent="." node_paths=PackedStringArray("AddressUi", "ErrorLabel")] [node name="Button" type="Button" parent="." node_paths=PackedStringArray("AddressField", "ErrorLabel")]
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
@@ -40,9 +113,10 @@ offset_right = 250.0
offset_bottom = 67.25 offset_bottom = 67.25
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
theme = ExtResource("1_wu84c")
text = "Connect" text = "Connect"
script = ExtResource("2_ekxnf") script = ExtResource("2_ekxnf")
AddressUi = NodePath("../Address") AddressField = NodePath("../Address")
ErrorLabel = NodePath("../Label") ErrorLabel = NodePath("../Label")
[node name="Label" type="Label" parent="."] [node name="Label" type="Label" parent="."]
@@ -58,4 +132,17 @@ offset_right = 68.5
offset_bottom = 98.149994 offset_bottom = 98.149994
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
theme = ExtResource("1_wu84c")
theme_override_colors/font_color = Color(1, 0, 0, 1) theme_override_colors/font_color = Color(1, 0, 0, 1)
[node name="TextureRect" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 5.0
offset_top = -69.0
offset_right = 69.0
offset_bottom = -5.0
grow_vertical = 0
texture = ExtResource("3_bqqt6")

View File

@@ -1,13 +1,25 @@
[gd_scene load_steps=4 format=3 uid="uid://b4tujjdhmk4h"] [gd_scene load_steps=11 format=3 uid="uid://b4tujjdhmk4h"]
[ext_resource type="Texture2D" uid="uid://ckmfi0cjgxgyk" path="res://assets/sprites/RedChip.png" id="1_qsflu"] [ext_resource type="Texture2D" uid="uid://ckmfi0cjgxgyk" path="res://assets/sprites/RedChip.png" id="1_qsflu"]
[ext_resource type="Script" uid="uid://dd5nu6037qsr0" path="res://scripts/red_chip.gd" id="1_tfypd"] [ext_resource type="Script" uid="uid://dd5nu6037qsr0" path="res://scripts/chip_sfx.gd" id="1_tfypd"]
[ext_resource type="AudioStream" uid="uid://dauf0pi1pkd3x" path="res://assets/sfx/chip_collide_1.ogg" id="2_g7r6w"]
[ext_resource type="AudioStream" uid="uid://b6b7b7sc038n7" path="res://assets/sfx/chip_collide_2.ogg" id="3_l66m7"]
[ext_resource type="AudioStream" uid="uid://c6a4wqoopu53j" path="res://assets/sfx/chip_collide_3.ogg" id="4_5isma"]
[ext_resource type="AudioStream" uid="uid://5liedrachob2" path="res://assets/sfx/chip_collide_4.ogg" id="5_sa1hx"]
[ext_resource type="AudioStream" uid="uid://dsyovynhhbmw8" path="res://assets/sfx/chip_collide_5.ogg" id="6_752ap"]
[ext_resource type="AudioStream" uid="uid://c0a5rl5q04noq" path="res://assets/sfx/chip_collide_6.ogg" id="7_ky42j"]
[ext_resource type="AudioStream" uid="uid://b6d73wiiqxles" path="res://assets/sfx/chip_collide_7.ogg" id="8_lgcne"]
[sub_resource type="CircleShape2D" id="CircleShape2D_tfypd"] [sub_resource type="CircleShape2D" id="CircleShape2D_tfypd"]
radius = 13.017083 radius = 12.8
[node name="RedChip" type="RigidBody2D"] [node name="RedChip" type="RigidBody2D" node_paths=PackedStringArray("audio_stream_player_2d")]
lock_rotation = true
contact_monitor = true
max_contacts_reported = 3
script = ExtResource("1_tfypd") script = ExtResource("1_tfypd")
audio_stream_player_2d = NodePath("AudioStreamPlayer2D")
sounds = Array[AudioStream]([ExtResource("2_g7r6w"), ExtResource("3_l66m7"), ExtResource("4_5isma"), ExtResource("5_sa1hx"), ExtResource("6_752ap"), ExtResource("7_ky42j"), ExtResource("8_lgcne")])
[node name="Sprite2D" type="Sprite2D" parent="."] [node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(3, 3) scale = Vector2(3, 3)
@@ -16,3 +28,8 @@ texture = ExtResource("1_qsflu")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
scale = Vector2(3, 3) scale = Vector2(3, 3)
shape = SubResource("CircleShape2D_tfypd") shape = SubResource("CircleShape2D_tfypd")
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."]
volume_db = -5.0
[connection signal="body_entered" from="." to="." method="_on_body_entered"]

View File

@@ -1,13 +1,25 @@
[gd_scene load_steps=4 format=3 uid="uid://lruk652t0xe5"] [gd_scene load_steps=11 format=3 uid="uid://lruk652t0xe5"]
[ext_resource type="Script" uid="uid://ddg7dv686sbrb" path="res://scripts/yellow_chip.gd" id="1_epi6l"] [ext_resource type="Script" uid="uid://dd5nu6037qsr0" path="res://scripts/chip_sfx.gd" id="1_epi6l"]
[ext_resource type="Texture2D" uid="uid://qy30emdgrk7o" path="res://assets/sprites/YellowChip.png" id="1_eu0sq"] [ext_resource type="Texture2D" uid="uid://qy30emdgrk7o" path="res://assets/sprites/YellowChip.png" id="1_eu0sq"]
[ext_resource type="AudioStream" uid="uid://dauf0pi1pkd3x" path="res://assets/sfx/chip_collide_1.ogg" id="2_ki13g"]
[ext_resource type="AudioStream" uid="uid://b6b7b7sc038n7" path="res://assets/sfx/chip_collide_2.ogg" id="3_kic5w"]
[ext_resource type="AudioStream" uid="uid://c6a4wqoopu53j" path="res://assets/sfx/chip_collide_3.ogg" id="4_nbcpr"]
[ext_resource type="AudioStream" uid="uid://5liedrachob2" path="res://assets/sfx/chip_collide_4.ogg" id="5_wq2sm"]
[ext_resource type="AudioStream" uid="uid://dsyovynhhbmw8" path="res://assets/sfx/chip_collide_5.ogg" id="6_ik045"]
[ext_resource type="AudioStream" uid="uid://c0a5rl5q04noq" path="res://assets/sfx/chip_collide_6.ogg" id="7_evobg"]
[ext_resource type="AudioStream" uid="uid://b6d73wiiqxles" path="res://assets/sfx/chip_collide_7.ogg" id="8_b44ux"]
[sub_resource type="CircleShape2D" id="CircleShape2D_epi6l"] [sub_resource type="CircleShape2D" id="CircleShape2D_epi6l"]
radius = 13.004272 radius = 12.8
[node name="YellowChip" type="RigidBody2D"] [node name="YellowChip" type="RigidBody2D" node_paths=PackedStringArray("audio_stream_player_2d")]
lock_rotation = true
contact_monitor = true
max_contacts_reported = 3
script = ExtResource("1_epi6l") script = ExtResource("1_epi6l")
audio_stream_player_2d = NodePath("AudioStreamPlayer2D")
sounds = Array[AudioStream]([ExtResource("2_ki13g"), ExtResource("3_kic5w"), ExtResource("4_nbcpr"), ExtResource("5_wq2sm"), ExtResource("6_ik045"), ExtResource("7_evobg"), ExtResource("8_b44ux")])
[node name="Sprite2D" type="Sprite2D" parent="."] [node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(3, 3) scale = Vector2(3, 3)
@@ -16,3 +28,8 @@ texture = ExtResource("1_eu0sq")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
scale = Vector2(3, 3) scale = Vector2(3, 3)
shape = SubResource("CircleShape2D_epi6l") shape = SubResource("CircleShape2D_epi6l")
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."]
volume_db = -5.0
[connection signal="body_entered" from="." to="." method="_on_body_entered"]

View File

@@ -1,39 +1,26 @@
using Godot; using Godot;
using System;
using System.Collections.Generic;
public partial class AdminControls : HBoxContainer public partial class AdminControls : HBoxContainer
{ {
[Export] public Button BecomeAdmin; [Export] public Button BecomeAdmin;
[Export] public Button StartTournament; [Export] public Button StartTournament;
[Export] public Button CancelTournament;
[Export] public Label Label; [Export] public Label Label;
[Export] public Slider Timeout; [Export] public Slider Timeout;
public override void _Ready() public override void _Ready()
{ {
Connection.Instance.OnBecomeAdmin += OnBecomeAdmin; Connection.Instance.OnBecomeAdmin += UpdateUI;
Connection.Instance.OnTournamentEnd += OnEndTournament; Connection.Instance.OnTournamentEnd += UpdateUI;
Connection.Instance.OnStartTournamentAck += UpdateUI;
Connection.Instance.OnCancelTournamentAck += UpdateUI;
Connection.Instance.OnGetDataAcks += UpdateUI;
StartTournament.Pressed += StartTournamentCommand; StartTournament.Pressed += () => Connection.Instance.StartTournament();
if (!Connection.Instance.IsAdmin || Connection.Instance.ActiveTournament) CancelTournament.Pressed += () => Connection.Instance.CancelTournament();
{
StartTournament.Hide();
}
if (!Connection.Instance.IsAdmin) UpdateUI();
{
Label.Hide();
Timeout.Hide();
}
Timeout.Value = Connection.Instance.CurrentWaitTimeout;
var time = Connection.Instance.CurrentWaitTimeout.ToString();
if (time.Length > 3)
{
time = time.Substring(0, 3);
}
Label.Text = "Wait To Move: " + time + "s ";
Timeout.ValueChanged += value => Timeout.ValueChanged += value =>
{ {
Connection.Instance.SetTournamentWait((float)value); Connection.Instance.SetTournamentWait((float)value);
@@ -50,24 +37,60 @@ public partial class AdminControls : HBoxContainer
public override void _ExitTree() public override void _ExitTree()
{ {
Connection.Instance.OnBecomeAdmin -= OnBecomeAdmin; Connection.Instance.OnBecomeAdmin -= UpdateUI;
Connection.Instance.OnTournamentEnd -= OnEndTournament; Connection.Instance.OnTournamentEnd -= UpdateUI;
Connection.Instance.OnStartTournamentAck -= UpdateUI;
Connection.Instance.OnCancelTournamentAck -= UpdateUI;
Connection.Instance.OnGetDataAcks -= UpdateUI;
} }
private void StartTournamentCommand() private void UpdateUI()
{ {
Connection.Instance.StartTournament(); if (!Connection.Instance.IsAdmin)
{
BecomeAdmin.Show();
StartTournament.Hide();
CancelTournament.Hide();
Label.Hide();
Timeout.Hide();
}
else
{
BecomeAdmin.Hide();
Label.Show();
Timeout.Show();
} }
private void OnEndTournament(List<(string, int)> playerScoreboard) if (Connection.Instance.IsAdmin && Connection.Instance.DemoMode)
{
StartTournament.Hide();
CancelTournament.Hide();
}
else if (Connection.Instance.IsAdmin && Connection.Instance.ActiveTournament)
{
StartTournament.Hide();
CancelTournament.Show();
}
else if (Connection.Instance.IsAdmin)
{ {
StartTournament.Show(); StartTournament.Show();
ShowTournamentScoreboard(playerScoreboard); CancelTournament.Hide();
}
Timeout.Value = Connection.Instance.CurrentWaitTimeout;
var time = Connection.Instance.CurrentWaitTimeout.ToString();
if (time.Length > 3)
{
time = time.Substring(0, 3);
}
Label.Text = "Wait To Move: " + time + "s ";
} }
private void ShowAuthPopup() private void ShowAuthPopup()
{ {
var authWindow = new Window(); var authWindow = new Window();
authWindow.Theme = GD.Load<Theme>("res://assets/theme.tres");
authWindow.AlwaysOnTop = true; authWindow.AlwaysOnTop = true;
authWindow.MaximizeDisabled = true; authWindow.MaximizeDisabled = true;
authWindow.Unresizable = true; authWindow.Unresizable = true;
@@ -75,7 +98,7 @@ public partial class AdminControls : HBoxContainer
authWindow.Size = new Vector2I(256, 128); authWindow.Size = new Vector2I(256, 128);
authWindow.CloseRequested += () => authWindow.CloseRequested += () =>
{ {
GetTree().Root.RemoveChild(authWindow); GetTree().Root.CallDeferred(Node.MethodName.RemoveChild, authWindow);
}; };
var vbox = new VBoxContainer(); var vbox = new VBoxContainer();
@@ -90,12 +113,30 @@ public partial class AdminControls : HBoxContainer
passwordBox.PlaceholderText = "Password"; passwordBox.PlaceholderText = "Password";
passwordBox.SetCustomMinimumSize(new Vector2(32, 32)); passwordBox.SetCustomMinimumSize(new Vector2(32, 32));
passwordBox.GuiInput += e =>
{
if (passwordBox.HasFocus() && e is InputEventKey inputEventKey && inputEventKey.IsPressed())
{
if (inputEventKey.KeyLabel == Key.Enter)
{
Connection.Instance.AdminAuth(passwordBox.Text);
GetTree().Root.CallDeferred(Node.MethodName.RemoveChild, authWindow);
GetViewport().SetInputAsHandled();
}
if (inputEventKey.KeyLabel == Key.Space)
{
GetViewport().SetInputAsHandled();
}
}
};
var button = new Button(); var button = new Button();
button.Text = "Login"; button.Text = "Login";
button.Pressed += () => button.Pressed += () =>
{ {
Connection.Instance.AdminAuth(passwordBox.Text); Connection.Instance.AdminAuth(passwordBox.Text);
GetTree().Root.RemoveChild(authWindow); GetTree().Root.CallDeferred(Node.MethodName.RemoveChild, authWindow);
}; };
vbox.AddChild(passwordBox); vbox.AddChild(passwordBox);
@@ -105,48 +146,4 @@ public partial class AdminControls : HBoxContainer
GetTree().Root.AddChild(authWindow); GetTree().Root.AddChild(authWindow);
} }
private void ShowTournamentScoreboard(List<(string, int)> playerScoreboard)
{
var scoreboardWindow = new Window();
scoreboardWindow.AlwaysOnTop = true;
scoreboardWindow.MaximizeDisabled = true;
scoreboardWindow.Unresizable = true;
scoreboardWindow.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
scoreboardWindow.Size = new Vector2I(256, 512);
scoreboardWindow.CloseRequested += () =>
{
GetTree().Root.RemoveChild(scoreboardWindow);
};
var tree = new Tree();
tree.HideRoot = true;
tree.Columns = 2;
tree.ColumnTitlesVisible = true;
tree.SetColumnTitle(0, "Player");
tree.SetColumnTitle(1, "Score");
var root = tree.CreateItem();
foreach ((string, int) entry in playerScoreboard)
{
var item = tree.CreateItem(root);
item.SetText(0, entry.Item1);
item.SetText(1, entry.Item2.ToString());
}
scoreboardWindow.AddChild(tree);
GetTree().Root.AddChild(scoreboardWindow);
}
private void OnBecomeAdmin()
{
BecomeAdmin.Hide();
if (!Connection.Instance.ActiveTournament)
{
StartTournament.Show();
}
Label.Show();
Timeout.Show();
}
} }

View File

@@ -1,9 +1,10 @@
using Godot; using Godot;
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
public partial class BoardScreen : Node2D { public partial class BoardScreen : Node2D
{
[Export] private AudioStream endingSfx;
[Export] private Theme theme;
private const string RED_CHIP_PATH = "res://scenes/red_chip.tscn"; private const string RED_CHIP_PATH = "res://scenes/red_chip.tscn";
private const string YELLOW_CHIP_PATH = "res://scenes/yellow_chip.tscn"; private const string YELLOW_CHIP_PATH = "res://scenes/yellow_chip.tscn";
@@ -25,10 +26,12 @@ public partial class BoardScreen : Node2D {
private Node2D player2Card; private Node2D player2Card;
private MatchData matchData; private MatchData matchData;
private List<(string, int)> moves;
private RigidBody2D[,] chips = new RigidBody2D[6, 7]; // 6 rows 7 cols | 0, 0 is top left private RigidBody2D[,] chips = new RigidBody2D[6, 7]; // 6 rows 7 cols | 0, 0 is top left
private bool _lastMove = false;
private float _lastMoveTimer = 2.5f;
private string _winner = "";
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() { public override void _Ready() {
// Node initialization // Node initialization
@@ -61,11 +64,10 @@ public partial class BoardScreen : Node2D {
player2Card.GetNode<Label>("Status").Hide(); player2Card.GetNode<Label>("Status").Hide();
} }
Connection.Instance.OnObserveWin += ObserveWin; Connection.Instance.OnObserveWin += OnObserveWin;
Connection.Instance.OnObserveDraw += ObserveDraw; Connection.Instance.OnObserveDraw += OnObserveDraw;
Connection.Instance.OnObserveTerminated += ObserveTerminated; Connection.Instance.OnObserveTerminated += OnObserveTerminated;
Connection.Instance.OnObserveMove += ObserveMove; Connection.Instance.OnObserveMove += ObserveMove;
Connection.Instance.OnTournamentEnd += ShowTournamentScoreboard;
} }
public override void _Process(double delta) public override void _Process(double delta)
@@ -89,56 +91,84 @@ public partial class BoardScreen : Node2D {
{ {
currentTimeout -= delta; currentTimeout -= delta;
} }
if (_lastMove)
{
_lastMoveTimer -= (float) delta;
}
if (_lastMoveTimer <= 0.0f)
{
if (_winner == "")
{
PopupMessage("Draw!");
}
else
{
PopupMessage(_winner + " wins!");
}
}
} }
public override void _ExitTree() public override void _ExitTree()
{ {
Connection.Instance.OnObserveWin -= ObserveWin; Connection.Instance.OnObserveWin -= OnObserveWin;
Connection.Instance.OnObserveDraw -= ObserveDraw; Connection.Instance.OnObserveDraw -= OnObserveDraw;
Connection.Instance.OnObserveTerminated -= ObserveTerminated; Connection.Instance.OnObserveTerminated -= OnObserveTerminated;
Connection.Instance.OnObserveMove -= ObserveMove; Connection.Instance.OnObserveMove -= ObserveMove;
Connection.Instance.OnTournamentEnd -= ShowTournamentScoreboard; }
private void OnObserveWin(string winner)
{
_lastMove = true;
_winner = winner;
player1Card.GetNode<Label>("Status").Hide();
player2Card.GetNode<Label>("Status").Hide();
}
private void OnObserveDraw()
{
_lastMove = true;
player1Card.GetNode<Label>("Status").Hide();
player2Card.GetNode<Label>("Status").Hide();
}
private void OnObserveTerminated()
{
PopupMessage("Match Terminated");
player1Card.GetNode<Label>("Status").Hide();
player2Card.GetNode<Label>("Status").Hide();
} }
private void ObserveMove(string username, int column) private void ObserveMove(string username, int column)
{ {
GD.Print(username);
if (username == matchData.player1) if (username == matchData.player1)
{ {
player1Card.GetNode<Label>("Status").Hide(); if (!_lastMove)
player2Card.GetNode<Label>("Status").Show(); player2Card.GetNode<Label>("Status").Show();
player1Card.GetNode<Label>("Status").Hide();
} }
else else
{ {
if (!_lastMove)
player1Card.GetNode<Label>("Status").Show(); player1Card.GetNode<Label>("Status").Show();
player2Card.GetNode<Label>("Status").Hide(); player2Card.GetNode<Label>("Status").Hide();
} }
Connection.Instance.PreviousMoves.Add((username, column)); Connection.Instance.PreviousMoves.Add((username, column));
} }
private void ObserveWin(string winner)
{
PopupMessage(winner + " wins!");
}
private void ObserveDraw()
{
PopupMessage("Draw!");
}
private void ObserveTerminated()
{
PopupMessage("Match Terminated");
}
private void PopupMessage(string message) private void PopupMessage(string message)
{ {
var popup = new Popup(); var popup = new Popup();
popup.AlwaysOnTop = true; popup.AlwaysOnTop = true;
popup.PopupCentered();
popup.Size = new Vector2I(200, 100); popup.Size = new Vector2I(200, 100);
popup.Theme = GD.Load<Theme>("res://assets/theme.tres");
var text = new Label(); var text = new Label();
text.Text = message; text.Text = message;
var sfx = new AudioStreamPlayer();
sfx.Stream = endingSfx;
sfx.VolumeDb = -2;
popup.AddChild(sfx);
popup.AddChild(text); popup.AddChild(text);
text.GrowHorizontal = Control.GrowDirection.Both; text.GrowHorizontal = Control.GrowDirection.Both;
text.GrowVertical = Control.GrowDirection.Both; text.GrowVertical = Control.GrowDirection.Both;
@@ -146,44 +176,14 @@ public partial class BoardScreen : Node2D {
text.VerticalAlignment = VerticalAlignment.Center; text.VerticalAlignment = VerticalAlignment.Center;
text.AnchorsPreset = (int) Control.LayoutPreset.FullRect; text.AnchorsPreset = (int) Control.LayoutPreset.FullRect;
popup.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen; popup.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
popup.PopupHide += () => popup.QueueFree();
GetTree().Root.AddChild(popup); GetTree().Root.AddChild(popup);
popup.PopupCentered();
sfx.Play();
popup.Show(); popup.Show();
TransitionToBracket(); TransitionToBracket();
} }
private void ShowTournamentScoreboard(List<(string, int)> playerScoreboard)
{
var scoreboardWindow = new Window();
scoreboardWindow.AlwaysOnTop = true;
scoreboardWindow.MaximizeDisabled = true;
scoreboardWindow.Unresizable = true;
scoreboardWindow.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
scoreboardWindow.Size = new Vector2I(256, 512);
scoreboardWindow.CloseRequested += () =>
{
GetTree().Root.RemoveChild(scoreboardWindow);
};
var tree = new Tree();
tree.HideRoot = true;
tree.Columns = 2;
tree.ColumnTitlesVisible = true;
tree.SetColumnTitle(0, "Player");
tree.SetColumnTitle(1, "Score");
var root = tree.CreateItem();
foreach ((string, int) entry in playerScoreboard)
{
var item = tree.CreateItem(root);
item.SetText(0, entry.Item1);
item.SetText(1, entry.Item2.ToString());
}
scoreboardWindow.AddChild(tree);
GetTree().Root.AddChild(scoreboardWindow);
}
private void TransitionToBracket() private void TransitionToBracket()
{ {
GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH); GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH);

View File

@@ -1,5 +1,4 @@
using Godot; using Godot;
using System;
using System.Collections.Generic; using System.Collections.Generic;
public partial class BracketScene : Control public partial class BracketScene : Control
@@ -45,6 +44,7 @@ public partial class BracketScene : Control
{ {
Players.Clear(); Players.Clear();
_playerList = playerList; _playerList = playerList;
_playerList.Sort((a, b) => a.username.CompareTo(b.username));
var root = Players.CreateItem(); var root = Players.CreateItem();
for (int i = 0; i < _playerList.Count; i++) for (int i = 0; i < _playerList.Count; i++)
{ {
@@ -54,7 +54,7 @@ public partial class BracketScene : Control
item.SetText(2, playerList[i].isPlaying ? "Yes" : "No"); item.SetText(2, playerList[i].isPlaying ? "Yes" : "No");
if (Connection.Instance.IsAdmin) if (Connection.Instance.IsAdmin)
{ {
item.AddButton(0, TerminateKickButton, i, false, "Kick"); // TODO item.AddButton(0, TerminateKickButton, i, false, "Kick");
} }
} }
} }
@@ -70,22 +70,30 @@ public partial class BracketScene : Control
item.SetText(0, matchList[i].matchId.ToString()); item.SetText(0, matchList[i].matchId.ToString());
item.SetText(1, matchList[i].player1); item.SetText(1, matchList[i].player1);
item.SetText(2, matchList[i].player2); item.SetText(2, matchList[i].player2);
item.AddButton(0, WatchButton, item.GetButtonCount(0), false, "Watch"); item.AddButton(0, WatchButton, i, false, "Watch");
if (Connection.Instance.IsAdmin) if (Connection.Instance.IsAdmin)
{ {
item.AddButton(0, TerminateKickButton, item.GetButtonCount(0), false, "Terminate"); item.AddButton(0, TerminateKickButton, 128 + i, false, "Terminate");
} }
} }
} }
private void WatchGame(TreeItem item, long column, long id, long mouseButtonIndex) private void WatchGame(TreeItem item, long column, long id, long mouseButtonIndex)
{ {
if (mouseButtonIndex == 1 && column == 0) if (mouseButtonIndex == 1 && column == 0 && id < 128)
{ {
Connection.Instance.SendWatchGame(_matchList[(int) id].matchId); Connection.Instance.SendWatchGame(_matchList[(int) id].matchId);
} }
} }
private void TerminateGame(TreeItem item, long column, long id, long mouseButtonIndex)
{
if (mouseButtonIndex == 1 && column == 0 && id - 128 >= 0 && _matchList[(int) id - 128] != null)
{
Connection.Instance.TerminateGame(_matchList[(int) id - 128].matchId);
}
}
private void KickPlayer(TreeItem item, long column, long id, long mouseButtonIndex) private void KickPlayer(TreeItem item, long column, long id, long mouseButtonIndex)
{ {
if (mouseButtonIndex == 1 && column == 0) if (mouseButtonIndex == 1 && column == 0)
@@ -94,14 +102,6 @@ public partial class BracketScene : Control
} }
} }
private void TerminateGame(TreeItem item, long column, long id, long mouseButtonIndex)
{
if (mouseButtonIndex == 1 && column == 0 && id - 1 > 0 && _matchList[(int) id - 1] != null)
{
Connection.Instance.TerminateGame(_matchList[(int) id - 1].matchId);
}
}
private void TransitionToBoard() private void TransitionToBoard()
{ {
GetTree().ChangeSceneToFile(BOARD_SCENE_PATH); GetTree().ChangeSceneToFile(BOARD_SCENE_PATH);

View File

@@ -1,23 +1,62 @@
using Godot; using Godot;
using System;
public partial class ConnectButtonUI : Button public partial class ConnectButtonUI : Button
{ {
[Export] public TextEdit AddressUi; [Export] public TextEdit AddressField;
[Export] public Label ErrorLabel; [Export] public Label ErrorLabel;
private const string BRACKET_SCENE_PATH = "res://scenes/bracket_view.tscn"; private const string BRACKET_SCENE_PATH = "res://scenes/bracket_view.tscn";
public override void _Ready()
{
Connection.Instance.OnWsConnectionSuccess += OnConnectionSuccess;
Connection.Instance.OnWsConnectionFailed += OnConnectionFailed;
if (Connection.Instance.LastUsedConnectionAddress.Length > 0)
{
AddressField.Text = Connection.Instance.LastUsedConnectionAddress;
}
if (Connection.Instance.LastError.Length > 0)
{
ErrorLabel.Text = Connection.Instance.LastError;
}
AddressField.GuiInput += e =>
{
if (AddressField.HasFocus() && e is InputEventKey inputEventKey && inputEventKey.IsPressed())
{
if (inputEventKey.KeyLabel == Key.Enter)
{
Connection.Instance.Connect(AddressField.Text);
GetViewport().SetInputAsHandled();
}
if (inputEventKey.KeyLabel == Key.Space)
{
GetViewport().SetInputAsHandled();
}
}
};
}
public override void _ExitTree()
{
Connection.Instance.OnWsConnectionSuccess -= OnConnectionSuccess;
Connection.Instance.OnWsConnectionFailed -= OnConnectionFailed;
}
public override void _Pressed() public override void _Pressed()
{ {
if (Connection.Instance.Connect(AddressUi.Text)) Connection.Instance.Connect(AddressField.Text);
}
private void OnConnectionSuccess()
{ {
GD.Print("Success!");
GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH); GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH);
} }
else
private void OnConnectionFailed()
{ {
ErrorLabel.Text = "Couldn't connect to server!"; ErrorLabel.Text = "Couldn't connect to server! " + Connection.Instance.LastError;
}
base._Pressed();
} }
} }

View File

@@ -9,8 +9,7 @@ public partial class Connection : Node
public static Connection Instance { get; private set; } public static Connection Instance { get; private set; }
private WebSocketPeer _webSocket = new WebSocketPeer(); private WebSocketPeer _webSocket = new ();
private bool _firstConnect = true;
private Thread _gameListThread; private Thread _gameListThread;
private bool _gameListThreadRunning; private bool _gameListThreadRunning;
@@ -31,87 +30,134 @@ public partial class Connection : Node
public event Action<List<MatchData>> OnUpdatedMatches; public event Action<List<MatchData>> OnUpdatedMatches;
public event Action<List<PlayerData>> OnUpdatedPlayers; public event Action<List<PlayerData>> OnUpdatedPlayers;
public event Action OnStartTournamentAck; public event Action OnStartTournamentAck;
public event Action<List<(string, int)>> OnTournamentEnd; public event Action OnTournamentEnd;
public event Action OnCancelTournamentAck;
public event Action OnBecomeAdmin; public event Action OnBecomeAdmin;
public event Action OnGetDataAcks;
public event Action OnWsConnectionSuccess;
public event Action OnWsConnectionFailed;
public event Action OnWsDisconnect;
// Already prints to console // Already prints to console
public event Action<string, string> OnError; public event Action<string> OnError;
public bool IsAdmin { get; private set; } public bool IsAdmin { get; private set; }
public bool IsPlayer { get; private set; } public bool IsPlayer { get; private set; }
public bool ActiveTournament { get; private set; } public bool ActiveTournament { get; private set; }
public List<(string, int)> PreviousMoves { get; private set; } = new List<(string, int)>(); public bool DemoMode { get; private set; }
public List<(string, int)> PreviousMoves { get; private set; } = [];
public double CurrentWaitTimeout { get; private set; } = 5.0; public double CurrentWaitTimeout { get; private set; } = 5.0;
public MatchData CurrentObservingMatch { get; private set; } public MatchData CurrentObservingMatch { get; private set; }
public string LastUsedConnectionAddress { get; private set; } = "";
public string LastError { get; private set; } = "";
private bool IsSocketOpen => _webSocket.GetReadyState() == WebSocketPeer.State.Open; private bool IsSocketOpen => _webSocket.GetReadyState() == WebSocketPeer.State.Open;
private bool _connecting = false;
private bool _connected = false;
private List<(string, int)> _lastScoreboard = [];
private bool _shouldShowTournamentResults = false;
private float _refreshGamePlayerListTimer = 5.0f;
public override void _Ready() public override void _Ready()
{ {
Instance = this; Instance = this;
_webSocket.SetHeartbeatInterval(5.0); _webSocket.SetHeartbeatInterval(5.0);
_webSocket.HeartbeatInterval = 5.0; _webSocket.HeartbeatInterval = 5.0;
Instance.OnWsDisconnect += () => GetTree().ChangeSceneToFile("res://scenes/main_menu.tscn");
} }
public bool Connect(string address) public void Connect(string address)
{ {
if (_webSocket.GetReadyState() == WebSocketPeer.State.Open) _connecting = true;
LastUsedConnectionAddress = address;
if (_connected)
{ {
return false; return;
} }
Error error = _webSocket.ConnectToUrl(address); Error error = _webSocket.ConnectToUrl(address);
if (error != Error.Ok) if (error != Error.Ok)
{ {
return false; LastError = error.ToString();
}
} }
_webSocket.Poll(); public override void _Process(double delta)
while (_webSocket.GetReadyState() == WebSocketPeer.State.Connecting)
{
_webSocket.Poll();
Thread.Sleep(TimeSpan.FromMilliseconds(5));
}
if (_webSocket.GetReadyState() != WebSocketPeer.State.Open)
{
return false;
}
_webSocket.SetHeartbeatInterval(5.0);
_webSocket.HeartbeatInterval = 5.0;
_firstConnect = false;
StartGameListRefreshLoop();
return true;
}
public override void _ExitTree()
{
StopGameListRefreshLoop();
}
public override void _PhysicsProcess(double delta)
{ {
_webSocket.Poll(); _webSocket.Poll();
WebSocketPeer.State state = _webSocket.GetReadyState(); WebSocketPeer.State state = _webSocket.GetReadyState();
if ((state == WebSocketPeer.State.Closed || state == WebSocketPeer.State.Closing) && !_firstConnect) if (state == WebSocketPeer.State.Open)
{ {
StopGameListRefreshLoop(); if (_connecting)
GD.PrintErr("Connection lost."); {
//GetTree().Quit(); _connecting = false;
_connected = true;
LastError = "";
OnWsConnectionSuccess?.Invoke();
UpdateGameList();
UpdatePlayerList();
_refreshGamePlayerListTimer = 5.0f;
} else if (_refreshGamePlayerListTimer <= 0.0f)
{
UpdateGameList();
UpdatePlayerList();
_refreshGamePlayerListTimer = 5.0f;
}
else
{
_refreshGamePlayerListTimer -= (float) delta;
} }
if (IsSocketOpen)
{
while (_webSocket.GetAvailablePacketCount() > 0) while (_webSocket.GetAvailablePacketCount() > 0)
{ {
string message = _webSocket.GetPacket().GetStringFromUtf8(); string message = _webSocket.GetPacket().GetStringFromUtf8();
HandleServerMessage(message); HandleServerMessage(message);
} }
if (_shouldShowTournamentResults)
{
var children = GetTree().Root.GetChildren();
foreach (var child in children)
{
if (child.Name.ToString() == "BracketView")
{
_shouldShowTournamentResults = false;
ShowTournamentScoreboard(_lastScoreboard);
}
}
}
}
else if (state == WebSocketPeer.State.Connecting)
{
// Do nothing
}
else if (state == WebSocketPeer.State.Closing)
{
// Do nothing
}
else if (state == WebSocketPeer.State.Closed)
{
if (_connecting)
{
_connecting = false;
OnWsConnectionFailed?.Invoke();
}
else if (_connected)
{
_connected = false;
IsAdmin = false;
CurrentWaitTimeout = 5.0;
ActiveTournament = false;
DemoMode = false;
_refreshGamePlayerListTimer = 5.0f;
var code = _webSocket.GetCloseCode();
var reason = _webSocket.GetCloseReason();
LastError = "Unexpected Disconnect. Reason: " + reason + ", Code: " + code;
GD.PrintErr("WebSocket closed with code: " + code + ", reason " + reason + ". Clean: " + (code != -1));
OnWsDisconnect?.Invoke();
}
} }
} }
@@ -151,45 +197,6 @@ public partial class Connection : Node
SendCommand("GAME", "LIST"); SendCommand("GAME", "LIST");
} }
private void StartGameListRefreshLoop()
{
if (_gameListThreadRunning)
{
return;
}
_gameListThreadRunning = true;
_gameListThread = new Thread(() =>
{
while (_gameListThreadRunning)
{
if (IsSocketOpen)
{
UpdateGameList();
UpdatePlayerList();
}
Thread.Sleep(TimeSpan.FromSeconds(5));
}
})
{
IsBackground = true
};
_gameListThread.Start();
}
private void StopGameListRefreshLoop()
{
if (!_gameListThreadRunning)
{
return;
}
_gameListThreadRunning = false;
_gameListThread?.Join();
_gameListThread = null;
}
public void UpdatePlayerList() public void UpdatePlayerList()
{ {
SendCommand("PLAYER", "LIST"); SendCommand("PLAYER", "LIST");
@@ -206,6 +213,16 @@ public partial class Connection : Node
SendCommand("ADMIN", "AUTH:" + password); SendCommand("ADMIN", "AUTH:" + password);
} }
public void GetMoveWait()
{
SendCommand("GET", "MOVE_WAIT");
}
public void GetTournamentStatus()
{
SendCommand("GET", "TOURNAMENT_STATUS");
}
// Admin commands // Admin commands
public void KickPlayer(string playerId) public void KickPlayer(string playerId)
@@ -220,6 +237,12 @@ public partial class Connection : Node
SendCommand("TOURNAMENT", "START"); SendCommand("TOURNAMENT", "START");
} }
public void CancelTournament()
{
if (!IsAdmin) return;
SendCommand("TOURNAMENT", "CANCEL");
}
public void TerminateGame(int matchID) public void TerminateGame(int matchID)
{ {
if (!IsAdmin) return; if (!IsAdmin) return;
@@ -308,7 +331,8 @@ public partial class Connection : Node
if (body == "AUTH:ACK") if (body == "AUTH:ACK")
{ {
IsAdmin = true; IsAdmin = true;
SetTournamentWait(5.0f); GetMoveWait();
GetTournamentStatus();
OnBecomeAdmin?.Invoke(); OnBecomeAdmin?.Invoke();
} }
@@ -316,9 +340,29 @@ public partial class Connection : Node
case "TOURNAMENT": case "TOURNAMENT":
HandleTournamentMessage(body); HandleTournamentMessage(body);
break; break;
case "GET":
if (body.StartsWith("MOVE_WAIT"))
{
CurrentWaitTimeout = double.Parse(body.Split(":")[1]);
}
else if (body.StartsWith("TOURNAMENT_STATUS"))
{
string status = body.Split(":")[1];
if (status != "DEMO")
{
ActiveTournament = bool.Parse(status);
}
else
{
ActiveTournament = false;
DemoMode = true;
}
}
OnGetDataAcks?.Invoke();
break;
case "ERROR": case "ERROR":
HandleErrorMessage(body); GD.PrintErr(message);
GD.PrintErr($"Error: {body}"); OnError?.Invoke(message);
break; break;
default: default:
GD.Print($"Unhandled server message: {message}"); GD.Print($"Unhandled server message: {message}");
@@ -350,13 +394,21 @@ public partial class Connection : Node
playerScoreboard.Add((data[0], int.Parse(data[1]))); playerScoreboard.Add((data[0], int.Parse(data[1])));
} }
OnTournamentEnd?.Invoke(playerScoreboard); _lastScoreboard = playerScoreboard;
_shouldShowTournamentResults = true;
OnTournamentEnd?.Invoke();
break; break;
} }
case "START": case "START":
{ {
OnStartTournamentAck?.Invoke();
ActiveTournament = true; ActiveTournament = true;
OnStartTournamentAck?.Invoke();
break;
}
case "CANCEL":
{
ActiveTournament = false;
OnCancelTournamentAck?.Invoke();
break; break;
} }
} }
@@ -496,18 +548,41 @@ public partial class Connection : Node
} }
} }
private void HandleErrorMessage(string body) public void ShowTournamentScoreboard(List<(string, int)> playerScoreboard)
{ {
if (string.IsNullOrWhiteSpace(body)) var scoreboardWindow = new Window();
scoreboardWindow.Theme = GD.Load<Theme>("res://assets/theme.tres");
scoreboardWindow.AlwaysOnTop = true;
scoreboardWindow.MaximizeDisabled = true;
scoreboardWindow.Unresizable = true;
scoreboardWindow.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
scoreboardWindow.Size = new Vector2I(256, 512);
scoreboardWindow.CloseRequested += () =>
{ {
OnError?.Invoke("UNKNOWN", string.Empty); GetTree().Root.RemoveChild(scoreboardWindow);
return; };
var tree = new Tree();
tree.HideRoot = true;
tree.Columns = 2;
tree.ColumnTitlesVisible = true;
tree.Theme = GD.Load<Theme>("res://assets/theme.tres");
tree.GrowHorizontal = Control.GrowDirection.Both;
tree.GrowVertical = Control.GrowDirection.Both;
tree.SetColumnTitle(0, "Player");
tree.SetColumnTitle(1, "Score");
var root = tree.CreateItem();
foreach ((string, int) entry in playerScoreboard)
{
var item = tree.CreateItem(root);
item.SetText(0, entry.Item1);
item.SetText(1, entry.Item2.ToString());
} }
string[] segments = body.Split(':'); scoreboardWindow.AddChild(tree);
string code = segments.Length > 0 ? segments[0].Trim().ToUpperInvariant() : "UNKNOWN"; tree.SetAnchorsPreset(Control.LayoutPreset.FullRect);
string detail = segments.Length > 1 ? string.Join(":", segments, 1, segments.Length - 1).Trim() : string.Empty;
OnError?.Invoke(code, detail); GetTree().Root.AddChild(scoreboardWindow);
} }
} }

View File

@@ -0,0 +1,8 @@
extends Node
func _ready() -> void:
var music = AudioStreamPlayer.new()
add_child(music)
music.stream = load("res://assets/music/jazz_music.mp3")
music.volume_db = -10
music.play()

View File

@@ -0,0 +1 @@
uid://hh10ct26xx1b

13
scripts/chip_sfx.gd Normal file
View File

@@ -0,0 +1,13 @@
extends RigidBody2D
@export var audio_stream_player_2d: AudioStreamPlayer2D
@export var velocity_for_sfx: float = 2.0
@export var sounds: Array[AudioStream]
func _on_body_entered(body: Node) -> void:
if body.name.begins_with("@RigidBody2D@") || body.name.begins_with("Floor"):
var rng = RandomNumberGenerator.new()
rng.randomize()
var index = rng.randi_range(0, sounds.size() - 1)
audio_stream_player_2d.stream = sounds[index]
audio_stream_player_2d.play();

View File

@@ -10,5 +10,5 @@ func _ready() -> void:
# Called every frame. 'delta' is the elapsed time since the previous frame. # Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void: func _process(_delta: float) -> void:
pass pass

View File

@@ -1,12 +0,0 @@
extends RigidBody2D
var color_ = "red";
# 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

View File

@@ -1,12 +0,0 @@
extends RigidBody2D
var color_ = "yellow";
# 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

View File

@@ -1 +0,0 @@
uid://ddg7dv686sbrb