Compare commits
24 Commits
v0.1.2
...
old-observ
@@ -2,3 +2,9 @@ root = true
|
|||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
tab_width = 2
|
||||||
|
|
||||||
|
[*.{cs,vb}]
|
||||||
|
dotnet_diagnostic.CA1050.severity = none
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,7 +3,11 @@
|
|||||||
/android/
|
/android/
|
||||||
.idea/
|
.idea/
|
||||||
*.csproj
|
*.csproj
|
||||||
|
*.csproj*
|
||||||
*.csproj.old
|
*.csproj.old
|
||||||
|
*.csproj.old*
|
||||||
*.sln
|
*.sln
|
||||||
*.sln.*
|
*.sln.*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
**/.DS_Store
|
||||||
|
Folder.DotSettings.user
|
||||||
|
|||||||
21
LICENSE
Normal file
21
LICENSE
Normal 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.
|
||||||
@@ -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
BIN
assets/.DS_Store
vendored
Binary file not shown.
1
assets/music/.gitattributes
vendored
Normal file
1
assets/music/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
jazz_music.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||||
BIN
assets/music/jazz_music.mp3
LFS
Normal file
BIN
assets/music/jazz_music.mp3
LFS
Normal file
Binary file not shown.
19
assets/music/jazz_music.mp3.import
Normal file
19
assets/music/jazz_music.mp3.import
Normal 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
|
||||||
BIN
assets/sfx/chip_collide_1.ogg
Normal file
BIN
assets/sfx/chip_collide_1.ogg
Normal file
Binary file not shown.
19
assets/sfx/chip_collide_1.ogg.import
Normal file
19
assets/sfx/chip_collide_1.ogg.import
Normal 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
|
||||||
BIN
assets/sfx/chip_collide_2.ogg
Normal file
BIN
assets/sfx/chip_collide_2.ogg
Normal file
Binary file not shown.
19
assets/sfx/chip_collide_2.ogg.import
Normal file
19
assets/sfx/chip_collide_2.ogg.import
Normal 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
|
||||||
BIN
assets/sfx/chip_collide_3.ogg
Normal file
BIN
assets/sfx/chip_collide_3.ogg
Normal file
Binary file not shown.
19
assets/sfx/chip_collide_3.ogg.import
Normal file
19
assets/sfx/chip_collide_3.ogg.import
Normal 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
|
||||||
BIN
assets/sfx/chip_collide_4.ogg
Normal file
BIN
assets/sfx/chip_collide_4.ogg
Normal file
Binary file not shown.
19
assets/sfx/chip_collide_4.ogg.import
Normal file
19
assets/sfx/chip_collide_4.ogg.import
Normal 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
|
||||||
BIN
assets/sfx/chip_collide_5.ogg
Normal file
BIN
assets/sfx/chip_collide_5.ogg
Normal file
Binary file not shown.
19
assets/sfx/chip_collide_5.ogg.import
Normal file
19
assets/sfx/chip_collide_5.ogg.import
Normal 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
|
||||||
BIN
assets/sfx/chip_collide_6.ogg
Normal file
BIN
assets/sfx/chip_collide_6.ogg
Normal file
Binary file not shown.
19
assets/sfx/chip_collide_6.ogg.import
Normal file
19
assets/sfx/chip_collide_6.ogg.import
Normal 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
|
||||||
BIN
assets/sfx/chip_collide_7.ogg
Normal file
BIN
assets/sfx/chip_collide_7.ogg
Normal file
Binary file not shown.
19
assets/sfx/chip_collide_7.ogg.import
Normal file
19
assets/sfx/chip_collide_7.ogg.import
Normal 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
BIN
assets/sfx/game_end.ogg
Normal file
Binary file not shown.
19
assets/sfx/game_end.ogg.import
Normal file
19
assets/sfx/game_end.ogg.import
Normal 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
BIN
assets/sprites/rpi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
40
assets/sprites/rpi.png.import
Normal file
40
assets/sprites/rpi.png.import
Normal 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
6
assets/theme.tres
Normal 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")
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio 2012
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "connect4-moderator-observer", "connect4-moderator-observer.csproj", "{88C189D2-5C9A-411D-8713-291AC0841D8C}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
|
||||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{88C189D2-5C9A-411D-8713-291AC0841D8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{88C189D2-5C9A-411D-8713-291AC0841D8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{88C189D2-5C9A-411D-8713-291AC0841D8C}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
|
||||||
{88C189D2-5C9A-411D-8713-291AC0841D8C}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
|
||||||
{88C189D2-5C9A-411D-8713-291AC0841D8C}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
|
||||||
{88C189D2-5C9A-411D-8713-291AC0841D8C}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
name="macOS"
|
name="macOS"
|
||||||
platform="macOS"
|
platform="macOS"
|
||||||
runnable=true
|
runnable=true
|
||||||
advanced_options=false
|
|
||||||
dedicated_server=false
|
dedicated_server=false
|
||||||
custom_features=""
|
custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
@@ -11,6 +10,11 @@ include_filter=""
|
|||||||
exclude_filter=""
|
exclude_filter=""
|
||||||
export_path="../../Downloads/connect4-moderator-observer.dmg"
|
export_path="../../Downloads/connect4-moderator-observer.dmg"
|
||||||
patches=PackedStringArray()
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
seed=0
|
seed=0
|
||||||
@@ -25,8 +29,9 @@ binary_format/architecture="universal"
|
|||||||
custom_template/debug=""
|
custom_template/debug=""
|
||||||
custom_template/release=""
|
custom_template/release=""
|
||||||
debug/export_console_wrapper=0
|
debug/export_console_wrapper=0
|
||||||
|
application/liquid_glass_icon="res://icon.icon"
|
||||||
application/icon="uid://dd7lvnidxr5ss"
|
application/icon="uid://dd7lvnidxr5ss"
|
||||||
application/icon_interpolation=4
|
application/icon_interpolation=0
|
||||||
application/bundle_identifier="com.abunchofknowitalls.connect4"
|
application/bundle_identifier="com.abunchofknowitalls.connect4"
|
||||||
application/signature=""
|
application/signature=""
|
||||||
application/app_category="Games"
|
application/app_category="Games"
|
||||||
@@ -49,7 +54,7 @@ xcode/xcode_build="14C18"
|
|||||||
codesign/codesign=3
|
codesign/codesign=3
|
||||||
codesign/installer_identity=""
|
codesign/installer_identity=""
|
||||||
codesign/apple_team_id="8S7C654DQ4"
|
codesign/apple_team_id="8S7C654DQ4"
|
||||||
codesign/identity="Developer ID Application: Joshua Higgins (8S7C654DQ4)"
|
codesign/identity="73BA692FE950ABC209210ACAA8AD412BD9C6C4A3"
|
||||||
codesign/entitlements/custom_file=""
|
codesign/entitlements/custom_file=""
|
||||||
codesign/entitlements/allow_jit_code_execution=false
|
codesign/entitlements/allow_jit_code_execution=false
|
||||||
codesign/entitlements/allow_unsigned_executable_memory=false
|
codesign/entitlements/allow_unsigned_executable_memory=false
|
||||||
@@ -185,10 +190,10 @@ privacy/collected_data/browsing_history/collected=false
|
|||||||
privacy/collected_data/browsing_history/linked_to_user=false
|
privacy/collected_data/browsing_history/linked_to_user=false
|
||||||
privacy/collected_data/browsing_history/used_for_tracking=false
|
privacy/collected_data/browsing_history/used_for_tracking=false
|
||||||
privacy/collected_data/browsing_history/collection_purposes=0
|
privacy/collected_data/browsing_history/collection_purposes=0
|
||||||
privacy/collected_data/search_hhistory/collected=false
|
privacy/collected_data/search_history/collected=false
|
||||||
privacy/collected_data/search_hhistory/linked_to_user=false
|
privacy/collected_data/search_history/linked_to_user=false
|
||||||
privacy/collected_data/search_hhistory/used_for_tracking=false
|
privacy/collected_data/search_history/used_for_tracking=false
|
||||||
privacy/collected_data/search_hhistory/collection_purposes=0
|
privacy/collected_data/search_history/collection_purposes=0
|
||||||
privacy/collected_data/user_id/collected=false
|
privacy/collected_data/user_id/collected=false
|
||||||
privacy/collected_data/user_id/linked_to_user=false
|
privacy/collected_data/user_id/linked_to_user=false
|
||||||
privacy/collected_data/user_id/used_for_tracking=false
|
privacy/collected_data/user_id/used_for_tracking=false
|
||||||
@@ -255,13 +260,16 @@ rm -rf \"{temp_dir}\""
|
|||||||
dotnet/include_scripts_content=false
|
dotnet/include_scripts_content=false
|
||||||
dotnet/include_debug_symbols=false
|
dotnet/include_debug_symbols=false
|
||||||
dotnet/embed_build_outputs=false
|
dotnet/embed_build_outputs=false
|
||||||
|
privacy/collected_data/search_hhistory/collected=false
|
||||||
|
privacy/collected_data/search_hhistory/linked_to_user=false
|
||||||
|
privacy/collected_data/search_hhistory/used_for_tracking=false
|
||||||
|
privacy/collected_data/search_hhistory/collection_purposes=0
|
||||||
|
|
||||||
[preset.1]
|
[preset.1]
|
||||||
|
|
||||||
name="Windows (x86_64)"
|
name="Windows (x86_64)"
|
||||||
platform="Windows Desktop"
|
platform="Windows Desktop"
|
||||||
runnable=true
|
runnable=true
|
||||||
advanced_options=true
|
|
||||||
dedicated_server=false
|
dedicated_server=false
|
||||||
custom_features=""
|
custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
@@ -269,6 +277,11 @@ include_filter=""
|
|||||||
exclude_filter=""
|
exclude_filter=""
|
||||||
export_path="../../Downloads/connect4-moderator-observer (win-x86_64).exe"
|
export_path="../../Downloads/connect4-moderator-observer (win-x86_64).exe"
|
||||||
patches=PackedStringArray()
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
seed=0
|
seed=0
|
||||||
@@ -332,7 +345,6 @@ dotnet/embed_build_outputs=true
|
|||||||
name="Windows (arm64)"
|
name="Windows (arm64)"
|
||||||
platform="Windows Desktop"
|
platform="Windows Desktop"
|
||||||
runnable=false
|
runnable=false
|
||||||
advanced_options=true
|
|
||||||
dedicated_server=false
|
dedicated_server=false
|
||||||
custom_features=""
|
custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
@@ -340,6 +352,11 @@ include_filter=""
|
|||||||
exclude_filter=""
|
exclude_filter=""
|
||||||
export_path="../../Downloads/connect4-moderator-observer (win-arm64).exe"
|
export_path="../../Downloads/connect4-moderator-observer (win-arm64).exe"
|
||||||
patches=PackedStringArray()
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
seed=0
|
seed=0
|
||||||
@@ -403,7 +420,6 @@ dotnet/embed_build_outputs=true
|
|||||||
name="Linux (x86_64)"
|
name="Linux (x86_64)"
|
||||||
platform="Linux"
|
platform="Linux"
|
||||||
runnable=true
|
runnable=true
|
||||||
advanced_options=true
|
|
||||||
dedicated_server=false
|
dedicated_server=false
|
||||||
custom_features=""
|
custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
@@ -411,6 +427,11 @@ include_filter=""
|
|||||||
exclude_filter=""
|
exclude_filter=""
|
||||||
export_path="../../Downloads/connect4-moderator-observer (linux-x86_64).out"
|
export_path="../../Downloads/connect4-moderator-observer (linux-x86_64).out"
|
||||||
patches=PackedStringArray()
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
seed=0
|
seed=0
|
||||||
@@ -449,7 +470,6 @@ dotnet/embed_build_outputs=true
|
|||||||
name="Linux (arm64)"
|
name="Linux (arm64)"
|
||||||
platform="Linux"
|
platform="Linux"
|
||||||
runnable=false
|
runnable=false
|
||||||
advanced_options=true
|
|
||||||
dedicated_server=false
|
dedicated_server=false
|
||||||
custom_features=""
|
custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
@@ -457,6 +477,11 @@ include_filter=""
|
|||||||
exclude_filter=""
|
exclude_filter=""
|
||||||
export_path="../../Downloads/connect4-moderator-observer (linux-arm64).out"
|
export_path="../../Downloads/connect4-moderator-observer (linux-arm64).out"
|
||||||
patches=PackedStringArray()
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
encryption_include_filters=""
|
encryption_include_filters=""
|
||||||
encryption_exclude_filters=""
|
encryption_exclude_filters=""
|
||||||
seed=0
|
seed=0
|
||||||
|
|||||||
BIN
icon.icon/Assets/icon.png
Normal file
BIN
icon.icon/Assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
40
icon.icon/Assets/icon.png.import
Normal file
40
icon.icon/Assets/icon.png.import
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dbn4ae2a7ao0t"
|
||||||
|
path="res://.godot/imported/icon.png-d1b269abce4c61d818c5589654ffbcc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.icon/Assets/icon.png"
|
||||||
|
dest_files=["res://.godot/imported/icon.png-d1b269abce4c61d818c5589654ffbcc2.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
|
||||||
37
icon.icon/icon.json
Normal file
37
icon.icon/icon.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"fill" : {
|
||||||
|
"automatic-gradient" : "extended-srgb:0.00000,0.53333,1.00000,1.00000"
|
||||||
|
},
|
||||||
|
"groups" : [
|
||||||
|
{
|
||||||
|
"layers" : [
|
||||||
|
{
|
||||||
|
"glass" : true,
|
||||||
|
"image-name" : "icon.png",
|
||||||
|
"name" : "icon",
|
||||||
|
"position" : {
|
||||||
|
"scale" : 0.2,
|
||||||
|
"translation-in-points" : [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shadow" : {
|
||||||
|
"kind" : "neutral",
|
||||||
|
"opacity" : 0.5
|
||||||
|
},
|
||||||
|
"translucency" : {
|
||||||
|
"enabled" : true,
|
||||||
|
"value" : 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"supported-platforms" : {
|
||||||
|
"circles" : [
|
||||||
|
"watchOS"
|
||||||
|
],
|
||||||
|
"squares" : "shared"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,18 +8,23 @@
|
|||||||
|
|
||||||
config_version=5
|
config_version=5
|
||||||
|
|
||||||
|
[animation]
|
||||||
|
|
||||||
|
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
|
||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Connect4 Moderator - Observer"
|
config/name="Connect4 Observer"
|
||||||
config/version="0.1.2"
|
config/version="1.0.1"
|
||||||
run/main_scene="uid://dcx5nvs0pa7me"
|
run/main_scene="uid://dcx5nvs0pa7me"
|
||||||
config/features=PackedStringArray("4.5", "C#", "Forward Plus")
|
config/features=PackedStringArray("4.6", "C#", "Forward Plus")
|
||||||
boot_splash/image="uid://dd7lvnidxr5ss"
|
boot_splash/image="uid://dd7lvnidxr5ss"
|
||||||
config/icon="uid://dd7lvnidxr5ss"
|
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 +32,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]
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
[gd_scene load_steps=14 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"]
|
||||||
|
[ext_resource type="Script" uid="uid://cwfg17tdbk44b" path="res://scripts/thinking.gd" id="5_wjs8a"]
|
||||||
[ext_resource type="Texture2D" uid="uid://8un28mol7qow" path="res://assets/sprites/BoardTileMap.png" id="6_i2o8i"]
|
[ext_resource type="Texture2D" uid="uid://8un28mol7qow" path="res://assets/sprites/BoardTileMap.png" id="6_i2o8i"]
|
||||||
[ext_resource type="PackedScene" uid="uid://pdean68jjg80" path="res://scenes/button_small.tscn" id="7_glh1q"]
|
[ext_resource type="PackedScene" uid="uid://pdean68jjg80" path="res://scenes/button_small.tscn" id="7_glh1q"]
|
||||||
[ext_resource type="Script" uid="uid://b3q4gq63qmx23" path="res://scripts/BackButton.cs" id="8_u1oi2"]
|
[ext_resource type="Script" uid="uid://b3q4gq63qmx23" path="res://scripts/BackButton.cs" id="8_u1oi2"]
|
||||||
@@ -33,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)
|
||||||
@@ -116,10 +121,10 @@ offset_left = -529.0
|
|||||||
offset_top = -292.0
|
offset_top = -292.0
|
||||||
offset_right = -427.0
|
offset_right = -427.0
|
||||||
offset_bottom = -284.0
|
offset_bottom = -284.0
|
||||||
theme_override_colors/font_color = Color(1, 0, 0, 1)
|
|
||||||
theme_override_fonts/font = ExtResource("3_rjcmr")
|
theme_override_fonts/font = ExtResource("3_rjcmr")
|
||||||
theme_override_font_sizes/font_size = 8
|
theme_override_font_sizes/font_size = 8
|
||||||
text = "NOT READY"
|
text = "THINKING"
|
||||||
|
script = ExtResource("5_wjs8a")
|
||||||
|
|
||||||
[node name="Player2Card" type="Node2D" parent="."]
|
[node name="Player2Card" type="Node2D" parent="."]
|
||||||
position = Vector2(989, -64)
|
position = Vector2(989, -64)
|
||||||
@@ -163,10 +168,10 @@ offset_left = -530.0
|
|||||||
offset_top = -220.0
|
offset_top = -220.0
|
||||||
offset_right = -428.0
|
offset_right = -428.0
|
||||||
offset_bottom = -212.0
|
offset_bottom = -212.0
|
||||||
theme_override_colors/font_color = Color(1, 0, 0, 1)
|
|
||||||
theme_override_fonts/font = ExtResource("3_rjcmr")
|
theme_override_fonts/font = ExtResource("3_rjcmr")
|
||||||
theme_override_font_sizes/font_size = 8
|
theme_override_font_sizes/font_size = 8
|
||||||
text = "NOT READY"
|
text = "THINKING"
|
||||||
|
script = ExtResource("5_wjs8a")
|
||||||
|
|
||||||
[node name="TileMap" type="TileMap" parent="."]
|
[node name="TileMap" type="TileMap" parent="."]
|
||||||
position = Vector2(39, 200)
|
position = Vector2(39, 200)
|
||||||
|
|||||||
@@ -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 "
|
||||||
|
|||||||
@@ -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="."]
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
public partial class AddressUI : TextEdit
|
public partial class AddressUI : TextEdit {
|
||||||
{
|
public override void _Ready() { Text = Connection.WS_DEFAULT_ADDRESS; }
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Text = Connection.WS_DEFAULT_ADDRESS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +1,83 @@
|
|||||||
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 += UpdateUI;
|
||||||
Connection.Instance.OnBecomeAdmin += OnBecomeAdmin;
|
Connection.Instance.OnTournamentEnd += UpdateUI;
|
||||||
Connection.Instance.OnTournamentEnd += OnEndTournament;
|
Connection.Instance.OnStartTournament += UpdateUI;
|
||||||
|
Connection.Instance.OnCancelTournamentAck += UpdateUI;
|
||||||
|
Connection.Instance.OnGetDataAcks += UpdateUI;
|
||||||
|
Connection.Instance.OnSetDataAcks += 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.SetMoveWait((float)value);
|
||||||
var time = Connection.Instance.CurrentWaitTimeout.ToString();
|
var time = Connection.Instance.CurrentWaitTimeout.ToString();
|
||||||
if (time.Length > 3)
|
if (time.Length > 3) {
|
||||||
{
|
|
||||||
time = time.Substring(0, 3);
|
time = time.Substring(0, 3);
|
||||||
}
|
}
|
||||||
Label.Text = "Wait To Move: " + time + "s ";
|
Label.Text = "Wait To Move: " + time + "s ";
|
||||||
};
|
};
|
||||||
|
|
||||||
BecomeAdmin.Pressed += ShowAuthPopup;
|
BecomeAdmin.Pressed += showAuthPopup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _ExitTree()
|
public override void _ExitTree() {
|
||||||
{
|
Connection.Instance.OnBecomeAdmin -= UpdateUI;
|
||||||
Connection.Instance.OnBecomeAdmin -= OnBecomeAdmin;
|
Connection.Instance.OnTournamentEnd -= UpdateUI;
|
||||||
Connection.Instance.OnTournamentEnd -= OnEndTournament;
|
Connection.Instance.OnStartTournament -= UpdateUI;
|
||||||
|
Connection.Instance.OnCancelTournamentAck -= UpdateUI;
|
||||||
|
Connection.Instance.OnGetDataAcks -= UpdateUI;
|
||||||
|
Connection.Instance.OnSetDataAcks -= UpdateUI;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartTournamentCommand()
|
private void UpdateUI() {
|
||||||
{
|
if (!Connection.Instance.IsAdmin) {
|
||||||
Connection.Instance.StartTournament();
|
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 != TournamentType.None) {
|
||||||
|
StartTournament.Hide();
|
||||||
|
CancelTournament.Show();
|
||||||
|
} else if (Connection.Instance.IsAdmin) {
|
||||||
StartTournament.Show();
|
StartTournament.Show();
|
||||||
ShowTournamentScoreboard(playerScoreboard);
|
CancelTournament.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowAuthPopup()
|
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() {
|
||||||
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 +85,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 +100,27 @@ 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 +130,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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
public partial class BackButton : TextureButton
|
public partial class BackButton : TextureButton {
|
||||||
{
|
|
||||||
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 _Pressed()
|
public override void _Pressed() {
|
||||||
{
|
transitionToBracket();
|
||||||
TransitionToBracket();
|
|
||||||
base._Pressed();
|
base._Pressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TransitionToBracket()
|
private void transitionToBracket() { GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH); }
|
||||||
{
|
|
||||||
GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
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";
|
||||||
@@ -24,18 +25,22 @@ 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
|
||||||
player1Card = GetNode<Node2D>("Player1Card");
|
player1Card = GetNode<Node2D>("Player1Card");
|
||||||
player2Card = GetNode<Node2D>("Player2Card");
|
player2Card = GetNode<Node2D>("Player2Card");
|
||||||
|
|
||||||
player1Card.GetNode<Label>("Name").Resized += () => setPlayerCardScale((player1Card.GetNode<Label>("Name").GetRect().Size.X + 7) / 16, player1Card);
|
player1Card.GetNode<Label>("Name").Resized += () =>
|
||||||
player2Card.GetNode<Label>("Name").Resized += () => setPlayerCardScale((player2Card.GetNode<Label>("Name").GetRect().Size.X + 7) / 16, player2Card);
|
setPlayerCardScale((player1Card.GetNode<Label>("Name").GetRect().Size.X + 7) / 16, player1Card);
|
||||||
|
player2Card.GetNode<Label>("Name").Resized += () =>
|
||||||
|
setPlayerCardScale((player2Card.GetNode<Label>("Name").GetRect().Size.X + 7) / 16, player2Card);
|
||||||
|
|
||||||
redChip = GD.Load<PackedScene>(RED_CHIP_PATH);
|
redChip = GD.Load<PackedScene>(RED_CHIP_PATH);
|
||||||
ylwChip = GD.Load<PackedScene>(YELLOW_CHIP_PATH);
|
ylwChip = GD.Load<PackedScene>(YELLOW_CHIP_PATH);
|
||||||
@@ -44,122 +49,118 @@ public partial class BoardScreen : Node2D {
|
|||||||
player1Card.GetNode<Label>("Name").Text = matchData.player1;
|
player1Card.GetNode<Label>("Name").Text = matchData.player1;
|
||||||
player2Card.GetNode<Label>("Name").Text = matchData.player2;
|
player2Card.GetNode<Label>("Name").Text = matchData.player2;
|
||||||
|
|
||||||
Connection.Instance.OnObserveWin += ObserveWin;
|
if (Connection.Instance.PreviousMoves.Count == 0) {
|
||||||
Connection.Instance.OnObserveDraw += ObserveDraw;
|
player1Card.GetNode<Label>("Status").Show();
|
||||||
Connection.Instance.OnObserveTerminated += ObserveTerminated;
|
player2Card.GetNode<Label>("Status").Hide();
|
||||||
Connection.Instance.OnObserveMove += ObserveMove;
|
} else if (Connection.Instance.PreviousMoves.Last().Item1 == matchData.player1) {
|
||||||
Connection.Instance.OnTournamentEnd += ShowTournamentScoreboard;
|
player1Card.GetNode<Label>("Status").Hide();
|
||||||
|
player2Card.GetNode<Label>("Status").Show();
|
||||||
|
} else {
|
||||||
|
player1Card.GetNode<Label>("Status").Show();
|
||||||
|
player2Card.GetNode<Label>("Status").Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Process(double delta)
|
Connection.Instance.OnObserveWin += OnObserveWin;
|
||||||
{
|
Connection.Instance.OnObserveDraw += OnObserveDraw;
|
||||||
if (Connection.Instance.PreviousMoves.Count != 0 && currentTimeout <= 0.0f)
|
Connection.Instance.OnObserveTerminated += OnObserveTerminated;
|
||||||
{
|
Connection.Instance.OnObserveMove += OnObserveMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta) {
|
||||||
|
if (Connection.Instance.PreviousMoves.Count != 0 && currentTimeout <= 0.0f) {
|
||||||
var move = Connection.Instance.PreviousMoves[0];
|
var move = Connection.Instance.PreviousMoves[0];
|
||||||
Connection.Instance.PreviousMoves.RemoveAt(0);
|
Connection.Instance.PreviousMoves.RemoveAt(0);
|
||||||
if (move.Item1 == matchData.player1)
|
if (move.Item1 == matchData.player1) {
|
||||||
{
|
|
||||||
spawnRed(move.Item2);
|
spawnRed(move.Item2);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
spawnYellow(move.Item2);
|
spawnYellow(move.Item2);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTimeout = MOVE_TIMEOUT_BEFORE_PLACE;
|
currentTimeout = MOVE_TIMEOUT_BEFORE_PLACE;
|
||||||
}
|
} else if (currentTimeout >= 0.0f) {
|
||||||
else if (currentTimeout >= 0.0f)
|
|
||||||
{
|
|
||||||
currentTimeout -= delta;
|
currentTimeout -= delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_lastMove) {
|
||||||
|
_lastMoveTimer -= (float)delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _ExitTree()
|
if (_lastMoveTimer <= 0.0f) {
|
||||||
{
|
if (_winner == "") {
|
||||||
Connection.Instance.OnObserveWin -= ObserveWin;
|
showPopupMessage("Draw!");
|
||||||
Connection.Instance.OnObserveDraw -= ObserveDraw;
|
} else {
|
||||||
Connection.Instance.OnObserveTerminated -= ObserveTerminated;
|
showPopupMessage(_winner + " wins!");
|
||||||
Connection.Instance.OnObserveMove -= ObserveMove;
|
}
|
||||||
Connection.Instance.OnTournamentEnd -= ShowTournamentScoreboard;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _ExitTree() {
|
||||||
|
Connection.Instance.OnObserveWin -= OnObserveWin;
|
||||||
|
Connection.Instance.OnObserveDraw -= OnObserveDraw;
|
||||||
|
Connection.Instance.OnObserveTerminated -= OnObserveTerminated;
|
||||||
|
Connection.Instance.OnObserveMove -= OnObserveMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
showPopupMessage("Match Terminated");
|
||||||
|
player1Card.GetNode<Label>("Status").Hide();
|
||||||
|
player2Card.GetNode<Label>("Status").Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnObserveMove(string username, int column) {
|
||||||
|
if (username == matchData.player1) {
|
||||||
|
if (!_lastMove)
|
||||||
|
player2Card.GetNode<Label>("Status").Show();
|
||||||
|
player1Card.GetNode<Label>("Status").Hide();
|
||||||
|
} else {
|
||||||
|
if (!_lastMove)
|
||||||
|
player1Card.GetNode<Label>("Status").Show();
|
||||||
|
player2Card.GetNode<Label>("Status").Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ObserveMove(string username, int column)
|
|
||||||
{
|
|
||||||
Connection.Instance.PreviousMoves.Add((username, column));
|
Connection.Instance.PreviousMoves.Add((username, column));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ObserveWin(string winner)
|
private void showPopupMessage(string message) {
|
||||||
{
|
|
||||||
PopupMessage(winner + " wins!");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ObserveDraw()
|
|
||||||
{
|
|
||||||
PopupMessage("Draw!");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ObserveTerminated()
|
|
||||||
{
|
|
||||||
PopupMessage("Match Terminated");
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
text.HorizontalAlignment = HorizontalAlignment.Center;
|
text.HorizontalAlignment = HorizontalAlignment.Center;
|
||||||
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)
|
private void transitionToBracket() { GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH); }
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determines if the column can have a new chip placed
|
* Determines if the column can have a new chip placed
|
||||||
@@ -167,16 +168,17 @@ public partial class BoardScreen : Node2D {
|
|||||||
* or
|
* or
|
||||||
* Will return row of board in which chip can be placed
|
* Will return row of board in which chip can be placed
|
||||||
*/
|
*/
|
||||||
public int canPlaceOnCol(int col) {
|
private int canPlaceOnCol(int col) {
|
||||||
if(col < 0 || col > 6) // Col out of range
|
if (col < 0 || col > 6) // Col out of range
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return getNextAvailRow(col);
|
return getNextAvailRow(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNextAvailRow(int col) {
|
private int getNextAvailRow(int col) {
|
||||||
for(int i = chips.GetLength(0) - 1; i >= 0; i--) { // Start at bottom
|
for (int i = chips.GetLength(0) - 1; i >= 0; i--) {
|
||||||
if(chips[i, col] == null)
|
// Start at bottom
|
||||||
|
if (chips[i, col] == null)
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,28 +187,30 @@ public partial class BoardScreen : Node2D {
|
|||||||
|
|
||||||
private void spawnRed(int col) {
|
private void spawnRed(int col) {
|
||||||
int row = canPlaceOnCol(col);
|
int row = canPlaceOnCol(col);
|
||||||
if(row == -1) {
|
if (row == -1) {
|
||||||
GD.Print("Invalid Placement!");
|
GD.Print("Invalid Placement!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RigidBody2D newNode = redChip.Instantiate<RigidBody2D>();
|
RigidBody2D newNode = redChip.Instantiate<RigidBody2D>();
|
||||||
AddChild(newNode);
|
AddChild(newNode);
|
||||||
newNode.Position = new Vector2(CHIP_SCALE * (CHIP_X_OFF + (CHIP_SIZE + CHIP_PADDING) * col), -(CHIP_SIZE + CHIP_PADDING) * 7);
|
newNode.Position = new Vector2(CHIP_SCALE * (CHIP_X_OFF + (CHIP_SIZE + CHIP_PADDING) * col),
|
||||||
|
-(CHIP_SIZE + CHIP_PADDING) * 7);
|
||||||
|
|
||||||
chips[row, col] = newNode;
|
chips[row, col] = newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnYellow(int col) {
|
private void spawnYellow(int col) {
|
||||||
int row = canPlaceOnCol(col);
|
int row = canPlaceOnCol(col);
|
||||||
if(row == -1) {
|
if (row == -1) {
|
||||||
GD.Print("Invalid Placement!");
|
GD.Print("Invalid Placement!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RigidBody2D newNode = ylwChip.Instantiate<RigidBody2D>();
|
RigidBody2D newNode = ylwChip.Instantiate<RigidBody2D>();
|
||||||
AddChild(newNode);
|
AddChild(newNode);
|
||||||
newNode.Position = new Vector2(CHIP_SCALE * (CHIP_X_OFF + (CHIP_SIZE + CHIP_PADDING) * col), -(CHIP_SIZE + CHIP_PADDING) * 7);
|
newNode.Position = new Vector2(CHIP_SCALE * (CHIP_X_OFF + (CHIP_SIZE + CHIP_PADDING) * col),
|
||||||
|
-(CHIP_SIZE + CHIP_PADDING) * 7);
|
||||||
|
|
||||||
chips[row, col] = newNode;
|
chips[row, col] = newNode;
|
||||||
}
|
}
|
||||||
@@ -219,12 +223,13 @@ public partial class BoardScreen : Node2D {
|
|||||||
cardCenter.Scale = new Vector2(x, 2);
|
cardCenter.Scale = new Vector2(x, 2);
|
||||||
cardCenter.Position = new Vector2(CARD_CENTER_X_DEFAULT + offX, cardCenter.Position.Y);
|
cardCenter.Position = new Vector2(CARD_CENTER_X_DEFAULT + offX, cardCenter.Position.Y);
|
||||||
|
|
||||||
cardRight.Position = new Vector2(CARD_CENTER_X_DEFAULT + offX * 2 + 8, cardRight.Position.Y); // 8 is a magic number (im too lazy) for the size of the card edge multipled by 2
|
cardRight.Position =
|
||||||
|
new Vector2(CARD_CENTER_X_DEFAULT + offX * 2 + 8,
|
||||||
|
cardRight.Position.Y); // 8 is a magic number (im too lazy) for the size of the card edge multipled by 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Direction
|
enum Direction {
|
||||||
{
|
|
||||||
UP,
|
UP,
|
||||||
UP_RIGHT,
|
UP_RIGHT,
|
||||||
RIGHT,
|
RIGHT,
|
||||||
|
|||||||
@@ -1,109 +1,91 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
public partial class BracketScene : Control
|
public partial class BracketScene : Control {
|
||||||
{
|
|
||||||
[Export] public Tree Players;
|
[Export] public Tree Players;
|
||||||
[Export] public Tree Matches;
|
[Export] public Tree Matches;
|
||||||
[Export] public Texture2D WatchButton;
|
[Export] public Texture2D WatchButton;
|
||||||
[Export] public Texture2D TerminateKickButton;
|
[Export] public Texture2D TerminateKickButton;
|
||||||
private const string BOARD_SCENE_PATH = "res://scenes/game.tscn";
|
private const string BOARD_SCENE_PATH = "res://scenes/game.tscn";
|
||||||
|
|
||||||
private List<PlayerData> _playerList;
|
private List<PlayerData> playerList;
|
||||||
private List<MatchData> _matchList;
|
private List<MatchData> matchList;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready() {
|
||||||
{
|
|
||||||
Players.SetColumnTitle(0, "Name");
|
Players.SetColumnTitle(0, "Name");
|
||||||
Players.SetColumnTitle(1, "Ready");
|
Players.SetColumnTitle(1, "Ready");
|
||||||
Players.SetColumnTitle(2, "Playing");
|
Players.SetColumnTitle(2, "Playing");
|
||||||
Players.HideRoot = true;
|
Players.HideRoot = true;
|
||||||
Players.ButtonClicked += KickPlayer;
|
Players.ButtonClicked += kickPlayer;
|
||||||
|
|
||||||
|
|
||||||
Matches.SetColumnTitle(0, "Match");
|
Matches.SetColumnTitle(0, "Match");
|
||||||
Matches.SetColumnTitle(1, "Red");
|
Matches.SetColumnTitle(1, "Red");
|
||||||
Matches.SetColumnTitle(2, "Yellow");
|
Matches.SetColumnTitle(2, "Yellow");
|
||||||
Matches.HideRoot = true;
|
Matches.HideRoot = true;
|
||||||
Matches.ButtonClicked += WatchGame;
|
Matches.ButtonClicked += watchGame;
|
||||||
Matches.ButtonClicked += TerminateGame;
|
Matches.ButtonClicked += terminateGame;
|
||||||
|
|
||||||
Connection.Instance.OnUpdatedPlayers += UpdatePlayers;
|
Connection.Instance.OnUpdatedPlayers += updatePlayers;
|
||||||
Connection.Instance.OnUpdatedMatches += UpdateMatches;
|
Connection.Instance.OnUpdatedMatches += updateMatches;
|
||||||
Connection.Instance.OnWatchGameAck += TransitionToBoard;
|
Connection.Instance.OnWatchGameAck += transitionToBoard;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _ExitTree()
|
public override void _ExitTree() {
|
||||||
{
|
Connection.Instance.OnUpdatedPlayers -= updatePlayers;
|
||||||
Connection.Instance.OnUpdatedPlayers -= UpdatePlayers;
|
Connection.Instance.OnUpdatedMatches -= updateMatches;
|
||||||
Connection.Instance.OnUpdatedMatches -= UpdateMatches;
|
Connection.Instance.OnWatchGameAck -= transitionToBoard;
|
||||||
Connection.Instance.OnWatchGameAck -= TransitionToBoard;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePlayers(List<PlayerData> playerList)
|
private void updatePlayers(List<PlayerData> newPlayerList) {
|
||||||
{
|
|
||||||
Players.Clear();
|
Players.Clear();
|
||||||
_playerList = playerList;
|
playerList = newPlayerList;
|
||||||
|
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++) {
|
||||||
{
|
|
||||||
var item = Players.CreateItem(root);
|
var item = Players.CreateItem(root);
|
||||||
item.SetText(0, playerList[i].username);
|
item.SetText(0, newPlayerList[i].Username);
|
||||||
item.SetText(1, playerList[i].isReady ? "Yes" : "No");
|
item.SetText(1, newPlayerList[i].IsReady ? "Yes" : "No");
|
||||||
item.SetText(2, playerList[i].isPlaying ? "Yes" : "No");
|
item.SetText(2, newPlayerList[i].IsPlaying ? "Yes" : "No");
|
||||||
if (Connection.Instance.IsAdmin)
|
if (Connection.Instance.IsAdmin) {
|
||||||
{
|
item.AddButton(0, TerminateKickButton, i, false, "Kick");
|
||||||
item.AddButton(0, TerminateKickButton, i, false, "Kick"); // TODO
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMatches(List<MatchData> matchList)
|
private void updateMatches(List<MatchData> newMatchList) {
|
||||||
{
|
|
||||||
Matches.Clear();
|
Matches.Clear();
|
||||||
_matchList = matchList;
|
matchList = newMatchList;
|
||||||
var root = Matches.CreateItem();
|
var root = Matches.CreateItem();
|
||||||
for (int i = 0; i < matchList.Count; i++)
|
for (int i = 0; i < newMatchList.Count; i++) {
|
||||||
{
|
|
||||||
var item = Matches.CreateItem(root);
|
var item = Matches.CreateItem(root);
|
||||||
item.SetText(0, matchList[i].matchId.ToString());
|
item.SetText(0, newMatchList[i].matchId.ToString());
|
||||||
item.SetText(1, matchList[i].player1);
|
item.SetText(1, newMatchList[i].player1);
|
||||||
item.SetText(2, matchList[i].player2);
|
item.SetText(2, newMatchList[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, 128 + i, false, "Terminate");
|
||||||
item.AddButton(0, TerminateKickButton, item.GetButtonCount(0), 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 && id < 128) {
|
||||||
if (mouseButtonIndex == 1 && column == 0)
|
Connection.Instance.SendWatchGame(matchList[(int)id].matchId);
|
||||||
{
|
|
||||||
Connection.Instance.SendWatchGame(_matchList[(int) id].matchId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void KickPlayer(TreeItem item, long column, long id, long mouseButtonIndex)
|
private void terminateGame(TreeItem item, long column, long id, long mouseButtonIndex) {
|
||||||
{
|
if (mouseButtonIndex == 1 && column == 0 && id - 128 >= 0 && matchList[(int)id - 128] != null) {
|
||||||
if (mouseButtonIndex == 1 && column == 0)
|
Connection.Instance.TerminateGame(matchList[(int)id - 128].matchId);
|
||||||
{
|
|
||||||
Connection.Instance.KickPlayer(_playerList[(int) id].username);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TerminateGame(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 && id - 1 > 0 && _matchList[(int) id - 1] != null)
|
Connection.Instance.KickPlayer(playerList[(int)id].Username);
|
||||||
{
|
|
||||||
Connection.Instance.TerminateGame(_matchList[(int) id - 1].matchId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TransitionToBoard()
|
private void transitionToBoard() { GetTree().ChangeSceneToFile(BOARD_SCENE_PATH); }
|
||||||
{
|
|
||||||
GetTree().ChangeSceneToFile(BOARD_SCENE_PATH);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
404
scripts/Connect4WinProbability.cs
Normal file
404
scripts/Connect4WinProbability.cs
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estimates Red/Yellow/Draw chances from a Connect 4 board state.
|
||||||
|
///
|
||||||
|
/// Implementation notes:
|
||||||
|
/// - Uses a near-perfect solver core (negamax + alpha-beta + transposition table) on a standard 7x6 bitboard.
|
||||||
|
/// - Converts the exact (perfect-play) score into a "chess.com-like" practical win% using:
|
||||||
|
/// (1) a sigmoid mapping of engine score -> win probability,
|
||||||
|
/// (2) an optional complexity adjustment based on how many moves preserve the best outcome.
|
||||||
|
/// </summary>
|
||||||
|
public static class Connect4WinProbability {
|
||||||
|
public const int Width = 7;
|
||||||
|
public const int Height = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cell content.
|
||||||
|
/// </summary>
|
||||||
|
public enum Cell {
|
||||||
|
None = 0,
|
||||||
|
Red = 1,
|
||||||
|
Yellow = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct Chances(double RedWinChance, double YellowWinChance, double DrawChance) {
|
||||||
|
public Chances Normalize() {
|
||||||
|
var sum = RedWinChance + YellowWinChance + DrawChance;
|
||||||
|
if (sum <= 0) return new Chances(0.5, 0.5, 0.0);
|
||||||
|
return new Chances(RedWinChance / sum, YellowWinChance / sum, DrawChance / sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estimate win/draw chances for the given board.
|
||||||
|
///
|
||||||
|
/// Expected board shape is [7,6]. The first index is column (0..6) and the second is row (0..5).
|
||||||
|
/// Row orientation is auto-detected: this method will accept either row=0 bottom or row=0 top,
|
||||||
|
/// as long as the position is gravity-valid.
|
||||||
|
///
|
||||||
|
/// <paramref name="toMove"/> must be Red or Yellow.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="board">2D array [7,6].</param>
|
||||||
|
/// <param name="toMove">Who is to play next.</param>
|
||||||
|
/// <param name="nodeBudget">Maximum explored nodes before falling back to the best-so-far estimate.</param>
|
||||||
|
/// <param name="enableComplexityAdjustment">If true, adjusts probabilities using move uniqueness/fragility.</param>
|
||||||
|
public static Chances Evaluate(Cell[,] board, Cell toMove, int nodeBudget = 350_000, bool enableComplexityAdjustment = true) {
|
||||||
|
if (board == null) throw new ArgumentNullException(nameof(board));
|
||||||
|
if (board.GetLength(0) != Width || board.GetLength(1) != Height)
|
||||||
|
throw new ArgumentException($"Board must be [{Width},{Height}]", nameof(board));
|
||||||
|
if (toMove is not Cell.Red and not Cell.Yellow)
|
||||||
|
throw new ArgumentException("toMove must be Cell.Red or Cell.Yellow", nameof(toMove));
|
||||||
|
|
||||||
|
if (!TryParseBoard(board, out var redBits, out var yellowBits)) {
|
||||||
|
// If the board is invalid, avoid lying with a confident number.
|
||||||
|
return new Chances(0.45, 0.45, 0.10).Normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
var mask = redBits | yellowBits;
|
||||||
|
var nbMoves = BitOperations.PopCount(mask);
|
||||||
|
|
||||||
|
// If someone has already won (shouldn't happen in a "next move" position, but observers might see it).
|
||||||
|
if (HasAlignment(redBits) && HasAlignment(yellowBits)) {
|
||||||
|
// Illegal: both cannot have 4-in-a-row in a legal game.
|
||||||
|
return new Chances(0.45, 0.45, 0.10).Normalize();
|
||||||
|
}
|
||||||
|
if (HasAlignment(redBits)) return new Chances(1.0, 0.0, 0.0);
|
||||||
|
if (HasAlignment(yellowBits)) return new Chances(0.0, 1.0, 0.0);
|
||||||
|
|
||||||
|
var position = Position.FromBitboards(mask, toMove == Cell.Red ? redBits : yellowBits);
|
||||||
|
|
||||||
|
// Solve the exact perfect-play score.
|
||||||
|
var tt = new TranspositionTable(1 << 20);
|
||||||
|
var solver = new Solver(tt, nodeBudget);
|
||||||
|
|
||||||
|
int bestScore = solver.Negamax(position, alpha: -Position.MaxScore, beta: Position.MaxScore);
|
||||||
|
|
||||||
|
// Optional complexity: score all immediate child moves (only up to 7) to see how "fragile" the outcome is.
|
||||||
|
int legalMoves = 0;
|
||||||
|
int bestMoves = 0;
|
||||||
|
int drawingMoves = 0;
|
||||||
|
|
||||||
|
if (enableComplexityAdjustment) {
|
||||||
|
for (int col = 0; col < Width; col++) {
|
||||||
|
if (!position.CanPlay(col)) continue;
|
||||||
|
legalMoves++;
|
||||||
|
|
||||||
|
var child = position;
|
||||||
|
child.Play(col);
|
||||||
|
// Reuse the same TT for speed.
|
||||||
|
int score = -solver.Negamax(child, alpha: -Position.MaxScore, beta: Position.MaxScore);
|
||||||
|
|
||||||
|
if (score == bestScore) bestMoves++;
|
||||||
|
if (score == 0) drawingMoves++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legalMoves == 0) {
|
||||||
|
// Board full.
|
||||||
|
return new Chances(0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int col = 0; col < Width; col++) if (position.CanPlay(col)) legalMoves++;
|
||||||
|
if (legalMoves == 0) return new Chances(0.0, 0.0, 1.0);
|
||||||
|
bestMoves = Math.Max(1, legalMoves / 2);
|
||||||
|
drawingMoves = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (pCurrentWin, pDraw) = ScoreToPracticalProbabilities(bestScore, nbMoves, legalMoves, bestMoves, drawingMoves);
|
||||||
|
var pCurrentLoss = Math.Max(0.0, 1.0 - pDraw - pCurrentWin);
|
||||||
|
|
||||||
|
// Map from current-player POV to Red/Yellow.
|
||||||
|
Chances result = toMove == Cell.Red
|
||||||
|
? new Chances(pCurrentWin, pCurrentLoss, pDraw)
|
||||||
|
: new Chances(pCurrentLoss, pCurrentWin, pDraw);
|
||||||
|
|
||||||
|
return result.Normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (double pCurrentWin, double pDraw) ScoreToPracticalProbabilities(
|
||||||
|
int score,
|
||||||
|
int nbMoves,
|
||||||
|
int legalMoves,
|
||||||
|
int bestMoves,
|
||||||
|
int drawingMoves
|
||||||
|
) {
|
||||||
|
// Normalize score by the maximum possible magnitude at this ply.
|
||||||
|
// The classic perfect-solver scoring is within [-21, 21] on a 7x6 board.
|
||||||
|
var maxAtPly = Math.Max(1, (Width * Height + 1 - nbMoves) / 2); // similar to gamesolver.org tutorial scoring
|
||||||
|
double s = Math.Clamp(score / (double)maxAtPly, -1.0, 1.0);
|
||||||
|
|
||||||
|
// Base win probability ignoring draws: a sigmoid curve similar in spirit to chess eval->win% mappings.
|
||||||
|
const double sigmoidScale = 3.0;
|
||||||
|
double pWinNoDraw = Sigmoid(s * sigmoidScale);
|
||||||
|
|
||||||
|
// Complexity/fragility: if only a few moves preserve the best outcome, the practical win% should be less extreme.
|
||||||
|
// complexity = 0 means many best moves (easy), 1 means only one best move (fragile).
|
||||||
|
double complexity = 1.0;
|
||||||
|
if (legalMoves > 0) {
|
||||||
|
complexity = 1.0 - Math.Clamp(bestMoves / (double)legalMoves, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blend toward 50% based on complexity.
|
||||||
|
// (If there are many good moves, keep the evaluation confident; if there's only one, flatten it.)
|
||||||
|
double flatten = 0.60 * complexity;
|
||||||
|
pWinNoDraw = Lerp(pWinNoDraw, 0.5, flatten);
|
||||||
|
|
||||||
|
// Draw propensity.
|
||||||
|
// - If perfect play draws (score == 0), put a significant mass on draw, more so if many moves keep it drawn.
|
||||||
|
// - If perfect play is decisive, keep draw small but non-zero (practical mistakes can still drift to a draw).
|
||||||
|
double drawMoveRatio = legalMoves > 0 ? (drawingMoves / (double)legalMoves) : 0.0;
|
||||||
|
|
||||||
|
double pDraw;
|
||||||
|
if (score == 0) {
|
||||||
|
pDraw = 0.55 + 0.35 * drawMoveRatio; // 0.55..0.90
|
||||||
|
// If draw is very "fragile" (few drawing moves), reduce draw slightly.
|
||||||
|
pDraw -= 0.10 * complexity;
|
||||||
|
} else {
|
||||||
|
// Keep it small, but let it rise a bit for positions where many moves still lead to a theoretical draw.
|
||||||
|
pDraw = 0.02 + 0.10 * drawMoveRatio;
|
||||||
|
// If the position is very complex, increase draw slightly (practical play drifts).
|
||||||
|
pDraw += 0.03 * complexity;
|
||||||
|
}
|
||||||
|
|
||||||
|
pDraw = Math.Clamp(pDraw, 0.0, 0.90);
|
||||||
|
|
||||||
|
// Combine.
|
||||||
|
double pWin = (1.0 - pDraw) * pWinNoDraw;
|
||||||
|
pWin = Math.Clamp(pWin, 0.0, 1.0 - pDraw);
|
||||||
|
|
||||||
|
return (pWin, pDraw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double Sigmoid(double x) => 1.0 / (1.0 + Math.Exp(-x));
|
||||||
|
private static double Lerp(double a, double b, double t) => a + (b - a) * Math.Clamp(t, 0.0, 1.0);
|
||||||
|
|
||||||
|
private static bool TryParseBoard(Cell[,] board, out ulong redBits, out ulong yellowBits) {
|
||||||
|
// We accept either row=0 bottom OR row=0 top as long as it is gravity-valid.
|
||||||
|
// We try both and select the first valid representation.
|
||||||
|
if (TryParseBoard(board, row0IsBottom: true, out redBits, out yellowBits)) return true;
|
||||||
|
if (TryParseBoard(board, row0IsBottom: false, out redBits, out yellowBits)) return true;
|
||||||
|
redBits = 0;
|
||||||
|
yellowBits = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseBoard(Cell[,] board, bool row0IsBottom, out ulong redBits, out ulong yellowBits) {
|
||||||
|
redBits = 0;
|
||||||
|
yellowBits = 0;
|
||||||
|
|
||||||
|
for (int col = 0; col < Width; col++) {
|
||||||
|
bool seenEmptyBelow = false;
|
||||||
|
for (int rowIdx = 0; rowIdx < Height; rowIdx++) {
|
||||||
|
int row = row0IsBottom ? rowIdx : (Height - 1 - rowIdx);
|
||||||
|
var cell = board[col, row];
|
||||||
|
|
||||||
|
if (cell == Cell.None) {
|
||||||
|
seenEmptyBelow = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seenEmptyBelow) {
|
||||||
|
// A disc is "floating" above an empty cell in this interpretation.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitRow = rowIdx; // bottom=0
|
||||||
|
ulong bit = 1UL << (col * (Height + 1) + bitRow);
|
||||||
|
|
||||||
|
if (cell == Cell.Red) redBits |= bit;
|
||||||
|
else if (cell == Cell.Yellow) yellowBits |= bit;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional sanity: overlap check.
|
||||||
|
return (redBits & yellowBits) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasAlignment(ulong pos) {
|
||||||
|
// Checks 4-in-a-row for a bitboard with (Height+1)=7 stride per column.
|
||||||
|
// Shifts correspond to:
|
||||||
|
// - 1: vertical
|
||||||
|
// - (Height+1): horizontal
|
||||||
|
// - (Height+1)+1: diagonal /
|
||||||
|
// - (Height+1)-1: diagonal \
|
||||||
|
int h1 = Height + 1;
|
||||||
|
|
||||||
|
// vertical
|
||||||
|
if (HasFour(pos, 1)) return true;
|
||||||
|
// horizontal
|
||||||
|
if (HasFour(pos, h1)) return true;
|
||||||
|
// diag / (up-right)
|
||||||
|
if (HasFour(pos, h1 + 1)) return true;
|
||||||
|
// diag \ (down-right)
|
||||||
|
if (HasFour(pos, h1 - 1)) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasFour(ulong pos, int shift) {
|
||||||
|
ulong m = pos & (pos >> shift);
|
||||||
|
return (m & (m >> (2 * shift))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Position {
|
||||||
|
// Bitboard representation (Pascal Pons / Tromp style):
|
||||||
|
// - mask: all occupied cells
|
||||||
|
// - current: stones of the player to move
|
||||||
|
public ulong Mask;
|
||||||
|
public ulong Current;
|
||||||
|
|
||||||
|
public const int MaxScore = (Width * Height + 1) / 2; // 21
|
||||||
|
|
||||||
|
private static readonly ulong[] BottomMask = new ulong[Width];
|
||||||
|
private static readonly ulong[] TopMask = new ulong[Width];
|
||||||
|
private static readonly ulong[] ColumnMask = new ulong[Width];
|
||||||
|
private static readonly ulong BoardMask;
|
||||||
|
|
||||||
|
static Position() {
|
||||||
|
for (int c = 0; c < Width; c++) {
|
||||||
|
BottomMask[c] = 1UL << (c * (Height + 1));
|
||||||
|
TopMask[c] = 1UL << (c * (Height + 1) + (Height - 1));
|
||||||
|
ulong colMask = 0;
|
||||||
|
for (int r = 0; r < Height; r++) colMask |= 1UL << (c * (Height + 1) + r);
|
||||||
|
ColumnMask[c] = colMask;
|
||||||
|
}
|
||||||
|
ulong bm = 0;
|
||||||
|
for (int c = 0; c < Width; c++) bm |= ColumnMask[c];
|
||||||
|
BoardMask = bm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Position FromBitboards(ulong mask, ulong currentToMoveBits) {
|
||||||
|
return new Position { Mask = mask, Current = currentToMoveBits };
|
||||||
|
}
|
||||||
|
|
||||||
|
public int NbMoves() => BitOperations.PopCount(Mask);
|
||||||
|
|
||||||
|
public bool CanPlay(int col) {
|
||||||
|
if ((uint)col >= Width) return false;
|
||||||
|
return (Mask & TopMask[col]) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play(int col) {
|
||||||
|
// Switch side-to-move by XOR with mask (classic trick).
|
||||||
|
Current ^= Mask;
|
||||||
|
// Drop a disc into the given column.
|
||||||
|
Mask |= Mask + BottomMask[col];
|
||||||
|
// Ensure Mask only contains board cells.
|
||||||
|
Mask &= BoardMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsWinningMove(int col) {
|
||||||
|
// Compute the position of the current player AFTER playing this column.
|
||||||
|
ulong pos = Current;
|
||||||
|
ulong m = Mask;
|
||||||
|
// play into column: get the bit for the new disc
|
||||||
|
ulong newMask = (m | (m + BottomMask[col])) & BoardMask;
|
||||||
|
ulong moveBit = newMask ^ m;
|
||||||
|
pos |= moveBit;
|
||||||
|
return HasAlignment(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Key() {
|
||||||
|
// Mix mask + current into a stable 64-bit key.
|
||||||
|
// Good enough for a fixed-size transposition table.
|
||||||
|
unchecked {
|
||||||
|
ulong x = Mask * 6364136223846793005UL + 1442695040888963407UL;
|
||||||
|
return x ^ (Current * 11400714819323198485UL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TranspositionTable {
|
||||||
|
private struct Entry {
|
||||||
|
public ulong Key;
|
||||||
|
public sbyte Value;
|
||||||
|
public byte Used;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Entry[] _entries;
|
||||||
|
private readonly int _mask;
|
||||||
|
|
||||||
|
public TranspositionTable(int sizePowerOfTwo) {
|
||||||
|
if (sizePowerOfTwo <= 0 || (sizePowerOfTwo & (sizePowerOfTwo - 1)) != 0)
|
||||||
|
throw new ArgumentException("TT size must be a power of two", nameof(sizePowerOfTwo));
|
||||||
|
_entries = new Entry[sizePowerOfTwo];
|
||||||
|
_mask = sizePowerOfTwo - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet(ulong key, out int value) {
|
||||||
|
ref var e = ref _entries[(int)key & _mask];
|
||||||
|
if (e.Used != 0 && e.Key == key) {
|
||||||
|
value = e.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
value = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Put(ulong key, int value) {
|
||||||
|
ref var e = ref _entries[(int)key & _mask];
|
||||||
|
e.Key = key;
|
||||||
|
e.Value = (sbyte)Math.Clamp(value, -127, 127);
|
||||||
|
e.Used = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Solver {
|
||||||
|
private readonly TranspositionTable _tt;
|
||||||
|
private readonly int _nodeBudget;
|
||||||
|
private int _nodes;
|
||||||
|
|
||||||
|
public Solver(TranspositionTable tt, int nodeBudget) {
|
||||||
|
_tt = tt;
|
||||||
|
_nodeBudget = Math.Max(10_000, nodeBudget);
|
||||||
|
_nodes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Negamax(Position p, int alpha, int beta) {
|
||||||
|
// Budget guard: if we run out, return a conservative estimate.
|
||||||
|
if (_nodes++ > _nodeBudget) return 0;
|
||||||
|
|
||||||
|
int moves = p.NbMoves();
|
||||||
|
if (moves >= Width * Height) return 0; // draw by full board
|
||||||
|
|
||||||
|
// Tight theoretical bounds for this ply (helps alpha-beta).
|
||||||
|
int max = (Width * Height + 1 - moves) / 2;
|
||||||
|
int min = -(Width * Height - moves) / 2;
|
||||||
|
if (alpha < min) alpha = min;
|
||||||
|
if (beta > max) beta = max;
|
||||||
|
if (alpha >= beta) return alpha;
|
||||||
|
|
||||||
|
// Immediate win check.
|
||||||
|
for (int col = 0; col < Width; col++) {
|
||||||
|
if (!p.CanPlay(col)) continue;
|
||||||
|
if (p.IsWinningMove(col)) return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong key = p.Key();
|
||||||
|
if (_tt.TryGet(key, out int cached)) return cached;
|
||||||
|
|
||||||
|
int best = min;
|
||||||
|
|
||||||
|
// Center-first move ordering (classic Connect 4 heuristic).
|
||||||
|
// Order: 3,4,2,5,1,6,0
|
||||||
|
Span<int> order = stackalloc int[Width] { 3, 4, 2, 5, 1, 6, 0 };
|
||||||
|
for (int i = 0; i < order.Length; i++) {
|
||||||
|
int col = order[i];
|
||||||
|
if (!p.CanPlay(col)) continue;
|
||||||
|
|
||||||
|
var child = p;
|
||||||
|
child.Play(col);
|
||||||
|
int score = -Negamax(child, -beta, -alpha);
|
||||||
|
|
||||||
|
if (score > best) best = score;
|
||||||
|
if (score > alpha) alpha = score;
|
||||||
|
if (alpha >= beta) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tt.Put(key, best);
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,47 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
|
||||||
|
|
||||||
public partial class ConnectButtonUI : Button
|
public partial class ConnectButtonUI : Button {
|
||||||
{
|
[Export] public TextEdit AddressField;
|
||||||
[Export] public TextEdit AddressUi;
|
|
||||||
[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 _Pressed()
|
public override void _Ready() {
|
||||||
{
|
Connection.Instance.OnWsConnectionSuccess += OnConnectionSuccess;
|
||||||
if (Connection.Instance.Connect(AddressUi.Text))
|
Connection.Instance.OnWsConnectionFailed += OnConnectionFailed;
|
||||||
{
|
|
||||||
GD.Print("Success!");
|
if (Connection.Instance.LastUsedConnectionAddress.Length > 0) {
|
||||||
GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH);
|
AddressField.Text = Connection.Instance.LastUsedConnectionAddress;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
if (Connection.Instance.LastError.Length > 0) {
|
||||||
ErrorLabel.Text = "Couldn't connect to server!";
|
ErrorLabel.Text = Connection.Instance.LastError;
|
||||||
}
|
}
|
||||||
base._Pressed();
|
|
||||||
|
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() { Connection.Instance.Connect(AddressField.Text); }
|
||||||
|
|
||||||
|
private void OnConnectionSuccess() { GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH); }
|
||||||
|
|
||||||
|
private void OnConnectionFailed() {
|
||||||
|
ErrorLabel.Text = "Couldn't connect to server! " + Connection.Instance.LastError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
public partial class Connection : Node
|
public partial class Connection : Node {
|
||||||
{
|
|
||||||
public const string WS_DEFAULT_ADDRESS = "wss://connect4.abunchofknowitalls.com";
|
public const string WS_DEFAULT_ADDRESS = "wss://connect4.abunchofknowitalls.com";
|
||||||
|
|
||||||
|
private static TournamentType? parseTournamentType(string type) {
|
||||||
|
switch (type) {
|
||||||
|
case "RoundRobin":
|
||||||
|
return TournamentType.RoundRobin;
|
||||||
|
case "false":
|
||||||
|
return TournamentType.None;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Connection Instance { get; private set; }
|
public static Connection Instance { get; private set; }
|
||||||
|
|
||||||
private WebSocketPeer _webSocket = new WebSocketPeer();
|
private WebSocketPeer webSocket = new WebSocketPeer();
|
||||||
private bool _firstConnect = true;
|
|
||||||
private Thread _gameListThread;
|
|
||||||
private bool _gameListThreadRunning;
|
|
||||||
|
|
||||||
public event Action OnConnected;
|
public event Action OnConnected;
|
||||||
public event Action OnReadyAcknowledged;
|
public event Action OnReadyAcknowledged;
|
||||||
public event Action<bool> OnGameStart;
|
public event Action<bool> OnGameStart;
|
||||||
@@ -30,225 +35,228 @@ public partial class Connection : Node
|
|||||||
public event Action<string, int> OnObserveMove;
|
public event Action<string, int> OnObserveMove;
|
||||||
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 OnStartTournament;
|
||||||
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 OnSetDataAcks;
|
||||||
|
|
||||||
|
public event Action<List<(string player1, string player2)>> OnUpdatedReservations;
|
||||||
|
|
||||||
|
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 TournamentType 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 player1, string player2)> Reservations { 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 double MaxTimeout { get; private set; } = 30.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;
|
||||||
|
|
||||||
public override void _Ready()
|
private bool isConnecting = false;
|
||||||
{
|
private bool isConnected = false;
|
||||||
|
private List<(string, int)> lastScoreboard = [];
|
||||||
|
private bool shouldShowTournamentResults = false;
|
||||||
|
private float refreshGamePlayerListTimer = 5.0f;
|
||||||
|
|
||||||
|
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) {
|
||||||
{
|
isConnecting = true;
|
||||||
if (_webSocket.GetReadyState() == WebSocketPeer.State.Open)
|
LastUsedConnectionAddress = address;
|
||||||
{
|
if (isConnected) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error error = _webSocket.ConnectToUrl(address);
|
Error error = webSocket.ConnectToUrl(address);
|
||||||
if (error != Error.Ok)
|
if (error != Error.Ok) {
|
||||||
{
|
LastError = error.ToString();
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_webSocket.Poll();
|
public override void _Process(double delta) {
|
||||||
|
webSocket.Poll();
|
||||||
while (_webSocket.GetReadyState() == WebSocketPeer.State.Connecting)
|
WebSocketPeer.State state = webSocket.GetReadyState();
|
||||||
{
|
if (state == WebSocketPeer.State.Open) {
|
||||||
_webSocket.Poll();
|
if (isConnecting) {
|
||||||
Thread.Sleep(TimeSpan.FromMilliseconds(5));
|
isConnecting = false;
|
||||||
|
isConnected = true;
|
||||||
|
LastError = "";
|
||||||
|
OnWsConnectionSuccess?.Invoke();
|
||||||
|
UpdateGameList();
|
||||||
|
UpdatePlayerList();
|
||||||
|
refreshGamePlayerListTimer = 5.0f;
|
||||||
|
} else if (refreshGamePlayerListTimer <= 0.0f) {
|
||||||
|
UpdateGameList();
|
||||||
|
UpdatePlayerList();
|
||||||
|
refreshGamePlayerListTimer = 5.0f;
|
||||||
|
} else {
|
||||||
|
refreshGamePlayerListTimer -= (float)delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_webSocket.GetReadyState() != WebSocketPeer.State.Open)
|
while (webSocket.GetAvailablePacketCount() > 0) {
|
||||||
{
|
string message = webSocket.GetPacket().GetStringFromUtf8();
|
||||||
return false;
|
handleServerMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_webSocket.SetHeartbeatInterval(5.0);
|
if (shouldShowTournamentResults) {
|
||||||
_webSocket.HeartbeatInterval = 5.0;
|
var children = GetTree().Root.GetChildren();
|
||||||
_firstConnect = false;
|
foreach (var child in children) {
|
||||||
StartGameListRefreshLoop();
|
if (child.Name.ToString() == "BracketView") {
|
||||||
return true;
|
shouldShowTournamentResults = false;
|
||||||
|
ShowTournamentScoreboard(lastScoreboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
StopGameListRefreshLoop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _PhysicsProcess(double delta)
|
|
||||||
{
|
|
||||||
_webSocket.Poll();
|
|
||||||
WebSocketPeer.State state = _webSocket.GetReadyState();
|
|
||||||
if ((state == WebSocketPeer.State.Closed || state == WebSocketPeer.State.Closing) && !_firstConnect)
|
|
||||||
{
|
|
||||||
StopGameListRefreshLoop();
|
|
||||||
GD.PrintErr("Connection lost.");
|
|
||||||
//GetTree().Quit();
|
|
||||||
}
|
}
|
||||||
|
} else if (state == WebSocketPeer.State.Connecting) {
|
||||||
if (IsSocketOpen)
|
// Do nothing
|
||||||
{
|
} else if (state == WebSocketPeer.State.Closing) {
|
||||||
while (_webSocket.GetAvailablePacketCount() > 0)
|
// Do nothing
|
||||||
{
|
} else if (state == WebSocketPeer.State.Closed) {
|
||||||
string message = _webSocket.GetPacket().GetStringFromUtf8();
|
if (isConnecting) {
|
||||||
HandleServerMessage(message);
|
isConnecting = false;
|
||||||
|
OnWsConnectionFailed?.Invoke();
|
||||||
|
} else if (isConnected) {
|
||||||
|
isConnected = false;
|
||||||
|
IsAdmin = false;
|
||||||
|
CurrentWaitTimeout = 5.0;
|
||||||
|
ActiveTournament = TournamentType.None;
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Player commands
|
// Player commands
|
||||||
public void SendConnect(string clientId)
|
public void SendConnect(string clientId) {
|
||||||
{
|
if (string.IsNullOrWhiteSpace(clientId)) {
|
||||||
if (string.IsNullOrWhiteSpace(clientId))
|
|
||||||
{
|
|
||||||
GD.PrintErr("Client ID is required to CONNECT.");
|
GD.PrintErr("Client ID is required to CONNECT.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId = clientId.Trim();
|
clientId = clientId.Trim();
|
||||||
|
|
||||||
if (clientId.Contains(":"))
|
if (clientId.Contains(":")) {
|
||||||
{
|
|
||||||
GD.PrintErr("Client ID cannot contain ':' characters.");
|
GD.PrintErr("Client ID cannot contain ':' characters.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SendCommand("CONNECT", clientId);
|
sendCommand("CONNECT", clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendReady()
|
public void SendDisconnect() { sendCommand("DISCONNECT"); }
|
||||||
{
|
public void SendReady() { sendCommand("READY"); }
|
||||||
SendCommand("READY");
|
public void SendPlay(int column) { sendCommand("PLAY", column.ToString()); }
|
||||||
}
|
|
||||||
|
|
||||||
public void SendPlay(int column)
|
|
||||||
{
|
|
||||||
SendCommand("PLAY", column.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Observer commands
|
// Observer commands
|
||||||
public void UpdateGameList()
|
public void UpdateGameList() { sendCommand("GAME", "LIST"); }
|
||||||
{
|
public void UpdatePlayerList() { sendCommand("PLAYER", "LIST"); }
|
||||||
SendCommand("GAME", "LIST");
|
public void SendWatchGame(int matchID) { sendCommand("GAME", "WATCH:" + matchID); }
|
||||||
}
|
public void AdminAuth(string password) {
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
SendCommand("PLAYER", "LIST");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SendWatchGame(int matchID)
|
|
||||||
{
|
|
||||||
SendCommand("GAME", "WATCH:" + matchID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AdminAuth(string password)
|
|
||||||
{
|
|
||||||
if (IsAdmin) return;
|
if (IsAdmin) return;
|
||||||
SendCommand("ADMIN", "AUTH:" + password);
|
sendCommand("ADMIN", "AUTH:" + password);
|
||||||
}
|
}
|
||||||
|
public void GetMoveWait() { sendCommand("GET", "MOVE_WAIT"); }
|
||||||
|
public void GetTournamentStatus() { sendCommand("GET", "TOURNAMENT_STATUS"); }
|
||||||
|
public void GetDemoMode() { sendCommand("GET", "DEMO_MODE"); }
|
||||||
|
public void GetMaxTimeout() { sendCommand("GET", "MAX_TIMEOUT"); }
|
||||||
|
|
||||||
// Admin commands
|
// Admin commands
|
||||||
public void KickPlayer(string playerId)
|
public void KickPlayer(string playerId) {
|
||||||
{
|
|
||||||
if (!IsAdmin) return;
|
if (!IsAdmin) return;
|
||||||
SendCommand("ADMIN", "KICK:" + playerId);
|
sendCommand("ADMIN", "KICK:" + playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartTournament()
|
public void StartTournament(string tournamentType = "RoundRobin") {
|
||||||
{
|
|
||||||
if (!IsAdmin) return;
|
if (!IsAdmin) return;
|
||||||
SendCommand("TOURNAMENT", "START");
|
sendCommand("TOURNAMENT", "START:" + tournamentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TerminateGame(int matchID)
|
public void CancelTournament() {
|
||||||
{
|
|
||||||
if (!IsAdmin) return;
|
if (!IsAdmin) return;
|
||||||
SendCommand("GAME", "TERMINATE:" + matchID);
|
sendCommand("TOURNAMENT", "CANCEL");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTournamentWait(float waitTime)
|
public void TerminateGame(int matchID) {
|
||||||
{
|
if (!IsAdmin) return;
|
||||||
|
sendCommand("GAME", "TERMINATE:" + matchID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AwardGameWinner(int matchID, string winnerUsername) {
|
||||||
|
if (!IsAdmin) return;
|
||||||
|
if (string.IsNullOrWhiteSpace(winnerUsername)) return;
|
||||||
|
sendCommand("GAME", "AWARD:" + matchID + ":" + winnerUsername.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMoveWait(float waitTime) {
|
||||||
if (!IsAdmin) return;
|
if (!IsAdmin) return;
|
||||||
CurrentWaitTimeout = waitTime;
|
CurrentWaitTimeout = waitTime;
|
||||||
SendCommand("TOURNAMENT", "WAIT:" + waitTime);
|
sendCommand("SET", "MOVE_WAIT:" + waitTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendCommand(string header, string body = "")
|
public void SetDemoMode(bool demoMode) {
|
||||||
{
|
if (!IsAdmin) return;
|
||||||
if (!IsSocketOpen)
|
DemoMode = demoMode;
|
||||||
{
|
sendCommand("SET", "DEMO_MODE:" + demoMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMaxTimeout(float maxTimeout) {
|
||||||
|
if (!IsAdmin) return;
|
||||||
|
MaxTimeout = maxTimeout;
|
||||||
|
sendCommand("SET", "MAX_TIMEOUT:" + maxTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddReservation(string player1Username, string player2Username) {
|
||||||
|
if (!IsAdmin) return;
|
||||||
|
sendCommand("RESERVATION", $"ADD:{player1Username},{player2Username}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteReservation(string player1Username, string player2Username) {
|
||||||
|
if (!IsAdmin) return;
|
||||||
|
sendCommand("RESERVATION", $"DELETE:{player1Username},{player2Username}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetReservations() {
|
||||||
|
if (!IsAdmin) return;
|
||||||
|
sendCommand("RESERVATION", "GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCommand(string header, string body = "") {
|
||||||
|
if (!IsSocketOpen) {
|
||||||
GD.PrintErr($"Cannot send {header}, socket is not open.");
|
GD.PrintErr($"Cannot send {header}, socket is not open.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string payload = string.IsNullOrEmpty(body) ? header : $"{header}:{body}";
|
string payload = string.IsNullOrEmpty(body) ? header : $"{header}:{body}";
|
||||||
_webSocket.SendText(payload);
|
webSocket.SendText(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleServerMessage(string message)
|
private void handleServerMessage(string message) {
|
||||||
{
|
if (string.IsNullOrWhiteSpace(message)) {
|
||||||
if (string.IsNullOrWhiteSpace(message))
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,68 +265,84 @@ public partial class Connection : Node
|
|||||||
string header;
|
string header;
|
||||||
string body;
|
string body;
|
||||||
int separatorIndex = message.IndexOf(':');
|
int separatorIndex = message.IndexOf(':');
|
||||||
if (separatorIndex >= 0)
|
if (separatorIndex >= 0) {
|
||||||
{
|
|
||||||
header = message.Substring(0, separatorIndex).Trim();
|
header = message.Substring(0, separatorIndex).Trim();
|
||||||
body = message.Substring(separatorIndex + 1).Trim();
|
body = message.Substring(separatorIndex + 1).Trim();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
header = message.Trim();
|
header = message.Trim();
|
||||||
body = string.Empty;
|
body = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
header = header.ToUpperInvariant();
|
header = header.ToUpperInvariant();
|
||||||
|
|
||||||
switch (header)
|
switch (header) {
|
||||||
{
|
|
||||||
case "CONNECT":
|
case "CONNECT":
|
||||||
if (body.Equals("ACK", StringComparison.OrdinalIgnoreCase))
|
if (body.Equals("ACK", StringComparison.OrdinalIgnoreCase)) {
|
||||||
{
|
|
||||||
IsPlayer = true;
|
IsPlayer = true;
|
||||||
OnConnected?.Invoke();
|
OnConnected?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "READY":
|
case "READY":
|
||||||
if (body.Equals("ACK", StringComparison.OrdinalIgnoreCase))
|
if (body.Equals("ACK", StringComparison.OrdinalIgnoreCase)) {
|
||||||
{
|
|
||||||
OnReadyAcknowledged?.Invoke();
|
OnReadyAcknowledged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "GAME":
|
case "GAME":
|
||||||
HandleGameMessage(body);
|
handleGameMessage(body);
|
||||||
break;
|
break;
|
||||||
case "PLAYER":
|
case "PLAYER":
|
||||||
HandlePlayerList(body);
|
handlePlayerList(body);
|
||||||
break;
|
break;
|
||||||
case "OPPONENT":
|
case "OPPONENT":
|
||||||
if (int.TryParse(body, out int column))
|
if (int.TryParse(body, out int column)) {
|
||||||
{
|
|
||||||
OnOpponentMove?.Invoke(column);
|
OnOpponentMove?.Invoke(column);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
GD.PrintErr($"Invalid opponent column: {body}");
|
GD.PrintErr($"Invalid opponent column: {body}");
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "ADMIN":
|
case "ADMIN":
|
||||||
if (body == "AUTH:ACK")
|
if (body == "AUTH:ACK") {
|
||||||
{
|
|
||||||
IsAdmin = true;
|
IsAdmin = true;
|
||||||
SetTournamentWait(5.0f);
|
GetMoveWait();
|
||||||
|
GetTournamentStatus();
|
||||||
OnBecomeAdmin?.Invoke();
|
OnBecomeAdmin?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "TOURNAMENT":
|
case "TOURNAMENT":
|
||||||
HandleTournamentMessage(body);
|
handleTournamentMessage(body);
|
||||||
|
break;
|
||||||
|
case "GET":
|
||||||
|
string data = body.Split(":")[1];
|
||||||
|
if (body.StartsWith("MOVE_WAIT")) {
|
||||||
|
CurrentWaitTimeout = double.Parse(data);
|
||||||
|
} else if (body.StartsWith("MAX_TIMEOUT")) {
|
||||||
|
MaxTimeout = double.Parse(data);
|
||||||
|
} else if (body.StartsWith("DEMO_MODE")) {
|
||||||
|
DemoMode = bool.Parse(data);
|
||||||
|
} else if (body.StartsWith("TOURNAMENT_STATUS")) {
|
||||||
|
TournamentType? type = parseTournamentType(data);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
GD.PrintErr($"Unhandled tournament type: {data}");
|
||||||
|
} else {
|
||||||
|
ActiveTournament = type.Value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GD.PrintErr($"Unhandled data get: {body}");
|
||||||
|
}
|
||||||
|
|
||||||
|
OnGetDataAcks?.Invoke();
|
||||||
|
break;
|
||||||
|
case "SET":
|
||||||
|
OnSetDataAcks?.Invoke();
|
||||||
break;
|
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}");
|
||||||
@@ -326,10 +350,8 @@ public partial class Connection : Node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleTournamentMessage(string body)
|
private void handleTournamentMessage(string body) {
|
||||||
{
|
if (string.IsNullOrWhiteSpace(body)) {
|
||||||
if (string.IsNullOrWhiteSpace(body))
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,57 +359,112 @@ public partial class Connection : Node
|
|||||||
string command = segments[0].Trim().ToUpperInvariant();
|
string command = segments[0].Trim().ToUpperInvariant();
|
||||||
string argument = segments.Length > 1 ? segments[1].Trim() : string.Empty;
|
string argument = segments.Length > 1 ? segments[1].Trim() : string.Empty;
|
||||||
|
|
||||||
switch (command)
|
switch (command) {
|
||||||
{
|
case "END": {
|
||||||
case "END":
|
ActiveTournament = TournamentType.None;
|
||||||
{
|
|
||||||
ActiveTournament = false;
|
|
||||||
List<(string, int)> playerScoreboard = new List<(string, int)>();
|
List<(string, int)> playerScoreboard = new List<(string, int)>();
|
||||||
string[] entries = segments[1].Split("|");
|
string[] entries = segments[1].Split("|");
|
||||||
foreach (string entry in entries)
|
foreach (string entry in entries) {
|
||||||
{
|
|
||||||
string[] data = entry.Split(',');
|
string[] data = entry.Split(',');
|
||||||
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": {
|
||||||
{
|
TournamentType? type = parseTournamentType(argument);
|
||||||
OnStartTournamentAck?.Invoke();
|
if (type == null) {
|
||||||
ActiveTournament = true;
|
GD.PrintErr($"Unhandled tournament type: {argument}");
|
||||||
|
} else {
|
||||||
|
ActiveTournament = type.Value;
|
||||||
|
OnStartTournament?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CANCEL": {
|
||||||
|
ActiveTournament = TournamentType.None;
|
||||||
|
OnCancelTournamentAck?.Invoke();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandlePlayerList(string body)
|
private void handleReservationMessage(string body) {
|
||||||
{
|
if (string.IsNullOrWhiteSpace(body)) {
|
||||||
if (string.IsNullOrWhiteSpace(body))
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] segments = body.Split(':');
|
string[] segments = body.Split(':');
|
||||||
string command = segments[0].Trim().ToUpperInvariant();
|
string command = segments[0].Trim().ToUpperInvariant();
|
||||||
string argument = segments.Length > 1 ? segments[1].Trim() : string.Empty;
|
|
||||||
|
|
||||||
switch (command)
|
switch (command) {
|
||||||
{
|
case "ADD": {
|
||||||
case "LIST":
|
if (segments.Length < 2) break;
|
||||||
{
|
string[] users = segments[1].Split(',');
|
||||||
|
if (users.Length != 2) break;
|
||||||
|
|
||||||
|
var p1 = users[0];
|
||||||
|
var p2 = users[1];
|
||||||
|
Reservations.Add((p1, p2));
|
||||||
|
OnUpdatedReservations?.Invoke(new List<(string player1, string player2)>(Reservations));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "DELETE": {
|
||||||
|
if (segments.Length < 2) break;
|
||||||
|
string[] users = segments[1].Split(',');
|
||||||
|
if (users.Length != 2) break;
|
||||||
|
|
||||||
|
var p1 = users[0];
|
||||||
|
var p2 = users[1];
|
||||||
|
Reservations.RemoveAll(r => r.player1 == p1 && r.player2 == p2);
|
||||||
|
OnUpdatedReservations?.Invoke(new List<(string player1, string player2)>(Reservations));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LIST": {
|
||||||
|
var reservations = new List<(string player1, string player2)>();
|
||||||
|
|
||||||
|
if (segments.Length >= 2 && !string.IsNullOrWhiteSpace(segments[1])) {
|
||||||
|
string[] entries = segments[1].Split('|');
|
||||||
|
foreach (string entry in entries) {
|
||||||
|
string[] users = entry.Split(',');
|
||||||
|
if (users.Length != 2) continue;
|
||||||
|
reservations.Add((users[0], users[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reservations = reservations;
|
||||||
|
OnUpdatedReservations?.Invoke(new List<(string player1, string player2)>(Reservations));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
GD.PrintErr($"Unhandled RESERVATION message: {body}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePlayerList(string body) {
|
||||||
|
if (string.IsNullOrWhiteSpace(body)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] segments = body.Split(':');
|
||||||
|
string command = segments[0].Trim().ToUpperInvariant();
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case "LIST": {
|
||||||
List<PlayerData> players = new List<PlayerData>();
|
List<PlayerData> players = new List<PlayerData>();
|
||||||
|
|
||||||
if (segments.Length < 2)
|
if (segments.Length < 2) {
|
||||||
{
|
|
||||||
OnUpdatedPlayers?.Invoke(players);
|
OnUpdatedPlayers?.Invoke(players);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] entries = segments[1].Split("|");
|
string[] entries = segments[1].Split("|");
|
||||||
foreach (string entry in entries)
|
foreach (string entry in entries) {
|
||||||
{
|
|
||||||
string[] data = entry.Split(',');
|
string[] data = entry.Split(',');
|
||||||
players.Add(new PlayerData(data[0], bool.Parse(data[1]), bool.Parse(data[2])));
|
players.Add(new PlayerData(data[0], bool.Parse(data[1]), bool.Parse(data[2])));
|
||||||
}
|
}
|
||||||
@@ -398,10 +475,8 @@ public partial class Connection : Node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleGameMessage(string body)
|
private void handleGameMessage(string body) {
|
||||||
{
|
if (string.IsNullOrWhiteSpace(body)) {
|
||||||
if (string.IsNullOrWhiteSpace(body))
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,10 +484,8 @@ public partial class Connection : Node
|
|||||||
string command = segments[0].Trim().ToUpperInvariant();
|
string command = segments[0].Trim().ToUpperInvariant();
|
||||||
string argument = segments.Length > 1 ? segments[1].Trim() : string.Empty;
|
string argument = segments.Length > 1 ? segments[1].Trim() : string.Empty;
|
||||||
|
|
||||||
if (IsPlayer)
|
if (IsPlayer) {
|
||||||
{
|
switch (command) {
|
||||||
switch (command)
|
|
||||||
{
|
|
||||||
case "START":
|
case "START":
|
||||||
bool isFirst = argument == "1" || argument.Equals("TRUE", StringComparison.OrdinalIgnoreCase);
|
bool isFirst = argument == "1" || argument.Equals("TRUE", StringComparison.OrdinalIgnoreCase);
|
||||||
OnGameStart?.Invoke(isFirst);
|
OnGameStart?.Invoke(isFirst);
|
||||||
@@ -433,11 +506,9 @@ public partial class Connection : Node
|
|||||||
GD.Print($"Unhandled GAME message: {body}");
|
GD.Print($"Unhandled GAME message: {body}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else // Regular observer/admin
|
||||||
else // Regular observer/admin
|
|
||||||
{
|
|
||||||
switch (command)
|
|
||||||
{
|
{
|
||||||
|
switch (command) {
|
||||||
case "WIN":
|
case "WIN":
|
||||||
OnObserveWin?.Invoke(segments[1]);
|
OnObserveWin?.Invoke(segments[1]);
|
||||||
break;
|
break;
|
||||||
@@ -453,15 +524,13 @@ public partial class Connection : Node
|
|||||||
case "LIST":
|
case "LIST":
|
||||||
List<MatchData> matches = new List<MatchData>();
|
List<MatchData> matches = new List<MatchData>();
|
||||||
|
|
||||||
if (segments.Length < 2)
|
if (segments.Length < 2) {
|
||||||
{
|
|
||||||
OnUpdatedMatches?.Invoke(matches);
|
OnUpdatedMatches?.Invoke(matches);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] entries = segments[1].Split("|");
|
string[] entries = segments[1].Split("|");
|
||||||
foreach (string entry in entries)
|
foreach (string entry in entries) {
|
||||||
{
|
|
||||||
string[] data = entry.Split(',');
|
string[] data = entry.Split(',');
|
||||||
matches.Add(new MatchData(int.Parse(data[0]), data[1], data[2]));
|
matches.Add(new MatchData(int.Parse(data[0]), data[1], data[2]));
|
||||||
}
|
}
|
||||||
@@ -472,21 +541,18 @@ public partial class Connection : Node
|
|||||||
CurrentObservingMatch = null;
|
CurrentObservingMatch = null;
|
||||||
PreviousMoves.Clear();
|
PreviousMoves.Clear();
|
||||||
string[] activeMatchData = segments[2].Split("|");
|
string[] activeMatchData = segments[2].Split("|");
|
||||||
if (activeMatchData.IsEmpty())
|
if (activeMatchData.IsEmpty()) {
|
||||||
{
|
|
||||||
string[] matchData = segments[2].Split(',');
|
string[] matchData = segments[2].Split(',');
|
||||||
CurrentObservingMatch = new MatchData(int.Parse(matchData[0]), matchData[1], matchData[2]);
|
CurrentObservingMatch = new MatchData(int.Parse(matchData[0]), matchData[1], matchData[2]);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
string[] matchData = activeMatchData[0].Split(',');
|
string[] matchData = activeMatchData[0].Split(',');
|
||||||
CurrentObservingMatch = new MatchData(int.Parse(matchData[0]), matchData[1], matchData[2]);
|
CurrentObservingMatch = new MatchData(int.Parse(matchData[0]), matchData[1], matchData[2]);
|
||||||
for (int i = 1; i < activeMatchData.Length; i++)
|
for (int i = 1; i < activeMatchData.Length; i++) {
|
||||||
{
|
|
||||||
string[] moveData = activeMatchData[i].Split(',');
|
string[] moveData = activeMatchData[i].Split(',');
|
||||||
PreviousMoves.Add((moveData[0], int.Parse(moveData[1])));
|
PreviousMoves.Add((moveData[0], int.Parse(moveData[1])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnWatchGameAck?.Invoke();
|
OnWatchGameAck?.Invoke();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -496,18 +562,36 @@ public partial class Connection : Node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleErrorMessage(string body)
|
public void ShowTournamentScoreboard(List<(string, int)> playerScoreboard) {
|
||||||
{
|
var scoreboardWindow = new Window();
|
||||||
if (string.IsNullOrWhiteSpace(body))
|
scoreboardWindow.Theme = GD.Load<Theme>("res://assets/theme.tres");
|
||||||
{
|
scoreboardWindow.AlwaysOnTop = true;
|
||||||
OnError?.Invoke("UNKNOWN", string.Empty);
|
scoreboardWindow.MaximizeDisabled = true;
|
||||||
return;
|
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.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
public class MatchData
|
public class MatchData {
|
||||||
{
|
|
||||||
public int matchId { get; private set; }
|
public int matchId { get; private set; }
|
||||||
public string player1 { get; private set; }
|
public string player1 { get; private set; }
|
||||||
public string player2 { get; private set; }
|
public string player2 { get; private set; }
|
||||||
|
|
||||||
public MatchData(int matchId, string player1, string player2)
|
public MatchData(int matchId, string player1, string player2) {
|
||||||
{
|
|
||||||
this.matchId = matchId;
|
this.matchId = matchId;
|
||||||
this.player1 = player1;
|
this.player1 = player1;
|
||||||
this.player2 = player2;
|
this.player2 = player2;
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
public class PlayerData
|
public class PlayerData {
|
||||||
{
|
public string Username { get; private set; }
|
||||||
public string username { get; private set; }
|
public bool IsReady { get; private set; }
|
||||||
public bool isReady { get; private set; }
|
public bool IsPlaying { get; private set; }
|
||||||
public bool isPlaying { get; private set; }
|
|
||||||
|
|
||||||
public PlayerData(string username, bool isReady, bool isPlaying)
|
public PlayerData(string username, bool isReady, bool isPlaying) {
|
||||||
{
|
Username = username;
|
||||||
this.username = username;
|
IsReady = isReady;
|
||||||
this.isReady = isReady;
|
IsPlaying = isPlaying;
|
||||||
this.isPlaying = isPlaying;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4
scripts/TournamentType.cs
Normal file
4
scripts/TournamentType.cs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
public enum TournamentType {
|
||||||
|
None,
|
||||||
|
RoundRobin
|
||||||
|
}
|
||||||
1
scripts/TournamentType.cs.uid
Normal file
1
scripts/TournamentType.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dwb6ioubllb24
|
||||||
8
scripts/background_music.gd
Normal file
8
scripts/background_music.gd
Normal 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()
|
||||||
1
scripts/background_music.gd.uid
Normal file
1
scripts/background_music.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://hh10ct26xx1b
|
||||||
13
scripts/chip_sfx.gd
Normal file
13
scripts/chip_sfx.gd
Normal 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();
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
10
scripts/thinking.gd
Normal file
10
scripts/thinking.gd
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
extends Label
|
||||||
|
|
||||||
|
@export var hue = 0.0
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
modulate = Color.from_hsv(hue, 1.0, 1.0, 1.0)
|
||||||
|
if hue < 1.0:
|
||||||
|
hue += 0.1 * delta
|
||||||
|
else:
|
||||||
|
hue = 0.0
|
||||||
1
scripts/thinking.gd.uid
Normal file
1
scripts/thinking.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cwfg17tdbk44b
|
||||||
@@ -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
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://ddg7dv686sbrb
|
|
||||||
Reference in New Issue
Block a user