24 Commits

61 changed files with 2074 additions and 1067 deletions

View File

@@ -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
View File

@@ -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
View File

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

View File

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

BIN
assets/.DS_Store vendored

Binary file not shown.

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

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

BIN
assets/music/jazz_music.mp3 LFS Normal file

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

BIN
assets/sfx/game_end.ogg Normal file

Binary file not shown.

View File

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

BIN
assets/sprites/rpi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

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

6
assets/theme.tres Normal file
View File

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

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View 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
View 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"
}
}

View File

@@ -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]

View File

@@ -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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,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;
}
} }

View File

@@ -1,152 +1,133 @@
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.OnTournamentEnd += UpdateUI;
Connection.Instance.OnStartTournament += UpdateUI;
Connection.Instance.OnCancelTournamentAck += UpdateUI;
Connection.Instance.OnGetDataAcks += UpdateUI;
Connection.Instance.OnSetDataAcks += UpdateUI;
StartTournament.Pressed += () => Connection.Instance.StartTournament();
CancelTournament.Pressed += () => Connection.Instance.CancelTournament();
UpdateUI();
Timeout.ValueChanged += value =>
{ {
Connection.Instance.OnBecomeAdmin += OnBecomeAdmin; Connection.Instance.SetMoveWait((float)value);
Connection.Instance.OnTournamentEnd += OnEndTournament; var time = Connection.Instance.CurrentWaitTimeout.ToString();
if (time.Length > 3) {
time = time.Substring(0, 3);
}
Label.Text = "Wait To Move: " + time + "s ";
};
StartTournament.Pressed += StartTournamentCommand; BecomeAdmin.Pressed += showAuthPopup;
if (!Connection.Instance.IsAdmin || Connection.Instance.ActiveTournament) }
{
StartTournament.Hide(); public override void _ExitTree() {
Connection.Instance.OnBecomeAdmin -= UpdateUI;
Connection.Instance.OnTournamentEnd -= UpdateUI;
Connection.Instance.OnStartTournament -= UpdateUI;
Connection.Instance.OnCancelTournamentAck -= UpdateUI;
Connection.Instance.OnGetDataAcks -= UpdateUI;
Connection.Instance.OnSetDataAcks -= UpdateUI;
}
private void UpdateUI() {
if (!Connection.Instance.IsAdmin) {
BecomeAdmin.Show();
StartTournament.Hide();
CancelTournament.Hide();
Label.Hide();
Timeout.Hide();
} else {
BecomeAdmin.Hide();
Label.Show();
Timeout.Show();
}
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();
CancelTournament.Hide();
}
Timeout.Value = Connection.Instance.CurrentWaitTimeout;
var time = Connection.Instance.CurrentWaitTimeout.ToString();
if (time.Length > 3) {
time = time.Substring(0, 3);
}
Label.Text = "Wait To Move: " + time + "s ";
}
private void showAuthPopup() {
var authWindow = new Window();
authWindow.Theme = GD.Load<Theme>("res://assets/theme.tres");
authWindow.AlwaysOnTop = true;
authWindow.MaximizeDisabled = true;
authWindow.Unresizable = true;
authWindow.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
authWindow.Size = new Vector2I(256, 128);
authWindow.CloseRequested += () =>
{
GetTree().Root.CallDeferred(Node.MethodName.RemoveChild, authWindow);
};
var vbox = new VBoxContainer();
vbox.LayoutMode = 1;
vbox.AnchorBottom = 1.0f;
vbox.AnchorRight = 1.0f;
vbox.GrowHorizontal = GrowDirection.Both;
vbox.GrowVertical = GrowDirection.Both;
vbox.Alignment = AlignmentMode.Center;
var passwordBox = new TextEdit();
passwordBox.PlaceholderText = "Password";
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 (!Connection.Instance.IsAdmin) if (inputEventKey.KeyLabel == Key.Space) {
{ GetViewport().SetInputAsHandled();
Label.Hide();
Timeout.Hide();
} }
}
};
Timeout.Value = Connection.Instance.CurrentWaitTimeout; var button = new Button();
var time = Connection.Instance.CurrentWaitTimeout.ToString(); button.Text = "Login";
if (time.Length > 3) button.Pressed += () =>
{
time = time.Substring(0, 3);
}
Label.Text = "Wait To Move: " + time + "s ";
Timeout.ValueChanged += value =>
{
Connection.Instance.SetTournamentWait((float)value);
var time = Connection.Instance.CurrentWaitTimeout.ToString();
if (time.Length > 3)
{
time = time.Substring(0, 3);
}
Label.Text = "Wait To Move: " + time + "s ";
};
BecomeAdmin.Pressed += ShowAuthPopup;
}
public override void _ExitTree()
{ {
Connection.Instance.OnBecomeAdmin -= OnBecomeAdmin; Connection.Instance.AdminAuth(passwordBox.Text);
Connection.Instance.OnTournamentEnd -= OnEndTournament; GetTree().Root.CallDeferred(Node.MethodName.RemoveChild, authWindow);
} };
private void StartTournamentCommand() vbox.AddChild(passwordBox);
{ vbox.AddChild(button);
Connection.Instance.StartTournament();
}
private void OnEndTournament(List<(string, int)> playerScoreboard) authWindow.AddChild(vbox);
{
StartTournament.Show();
ShowTournamentScoreboard(playerScoreboard);
}
private void ShowAuthPopup() GetTree().Root.AddChild(authWindow);
{ }
var authWindow = new Window();
authWindow.AlwaysOnTop = true;
authWindow.MaximizeDisabled = true;
authWindow.Unresizable = true;
authWindow.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
authWindow.Size = new Vector2I(256, 128);
authWindow.CloseRequested += () =>
{
GetTree().Root.RemoveChild(authWindow);
};
var vbox = new VBoxContainer();
vbox.LayoutMode = 1;
vbox.AnchorBottom = 1.0f;
vbox.AnchorRight = 1.0f;
vbox.GrowHorizontal = GrowDirection.Both;
vbox.GrowVertical = GrowDirection.Both;
vbox.Alignment = AlignmentMode.Center;
var passwordBox = new TextEdit();
passwordBox.PlaceholderText = "Password";
passwordBox.SetCustomMinimumSize(new Vector2(32, 32));
var button = new Button();
button.Text = "Login";
button.Pressed += () =>
{
Connection.Instance.AdminAuth(passwordBox.Text);
GetTree().Root.RemoveChild(authWindow);
};
vbox.AddChild(passwordBox);
vbox.AddChild(button);
authWindow.AddChild(vbox);
GetTree().Root.AddChild(authWindow);
}
private void ShowTournamentScoreboard(List<(string, int)> playerScoreboard)
{
var scoreboardWindow = new Window();
scoreboardWindow.AlwaysOnTop = true;
scoreboardWindow.MaximizeDisabled = true;
scoreboardWindow.Unresizable = true;
scoreboardWindow.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
scoreboardWindow.Size = new Vector2I(256, 512);
scoreboardWindow.CloseRequested += () =>
{
GetTree().Root.RemoveChild(scoreboardWindow);
};
var tree = new Tree();
tree.HideRoot = true;
tree.Columns = 2;
tree.ColumnTitlesVisible = true;
tree.SetColumnTitle(0, "Player");
tree.SetColumnTitle(1, "Score");
var root = tree.CreateItem();
foreach ((string, int) entry in playerScoreboard)
{
var item = tree.CreateItem(root);
item.SetText(0, entry.Item1);
item.SetText(1, entry.Item2.ToString());
}
scoreboardWindow.AddChild(tree);
GetTree().Root.AddChild(scoreboardWindow);
}
private void OnBecomeAdmin()
{
BecomeAdmin.Hide();
if (!Connection.Instance.ActiveTournament)
{
StartTournament.Show();
}
Label.Show();
Timeout.Show();
}
} }

View File

@@ -1,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);
}
} }

View File

@@ -1,236 +1,241 @@
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";
private const string BRACKET_SCENE_PATH = "res://scenes/bracket_view.tscn"; private const string BRACKET_SCENE_PATH = "res://scenes/bracket_view.tscn";
private const int CHIP_SCALE = 3; private const int CHIP_SCALE = 3;
private const int CHIP_SIZE = 24; private const int CHIP_SIZE = 24;
private const int CHIP_PADDING = 2; private const int CHIP_PADDING = 2;
private const int CHIP_X_OFF = -(CHIP_SIZE + CHIP_PADDING) * 7 / 2 + (CHIP_SIZE + CHIP_PADDING) / 2; private const int CHIP_X_OFF = -(CHIP_SIZE + CHIP_PADDING) * 7 / 2 + (CHIP_SIZE + CHIP_PADDING) / 2;
private const int Y_OFF = 300; private const int Y_OFF = 300;
private const int CARD_CENTER_X_DEFAULT = -536; private const int CARD_CENTER_X_DEFAULT = -536;
private const double MOVE_TIMEOUT_BEFORE_PLACE = 1.0f; private const double MOVE_TIMEOUT_BEFORE_PLACE = 1.0f;
private double currentTimeout = 0.0f; private double currentTimeout = 0.0f;
private PackedScene redChip; private PackedScene redChip;
private PackedScene ylwChip; private PackedScene ylwChip;
private Node2D player1Card; private Node2D player1Card;
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);
matchData = Connection.Instance.CurrentObservingMatch; matchData = Connection.Instance.CurrentObservingMatch;
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;
var move = Connection.Instance.PreviousMoves[0]; }
Connection.Instance.PreviousMoves.RemoveAt(0);
if (move.Item1 == matchData.player1)
{
spawnRed(move.Item2);
}
else
{
spawnYellow(move.Item2);
}
currentTimeout = MOVE_TIMEOUT_BEFORE_PLACE; public override void _Process(double delta) {
} if (Connection.Instance.PreviousMoves.Count != 0 && currentTimeout <= 0.0f) {
else if (currentTimeout >= 0.0f) var move = Connection.Instance.PreviousMoves[0];
{ Connection.Instance.PreviousMoves.RemoveAt(0);
currentTimeout -= delta; if (move.Item1 == matchData.player1) {
} spawnRed(move.Item2);
} else {
spawnYellow(move.Item2);
}
currentTimeout = MOVE_TIMEOUT_BEFORE_PLACE;
} else if (currentTimeout >= 0.0f) {
currentTimeout -= delta;
} }
public override void _ExitTree() if (_lastMove) {
{ _lastMoveTimer -= (float)delta;
Connection.Instance.OnObserveWin -= ObserveWin;
Connection.Instance.OnObserveDraw -= ObserveDraw;
Connection.Instance.OnObserveTerminated -= ObserveTerminated;
Connection.Instance.OnObserveMove -= ObserveMove;
Connection.Instance.OnTournamentEnd -= ShowTournamentScoreboard;
} }
private void ObserveMove(string username, int column) if (_lastMoveTimer <= 0.0f) {
{ if (_winner == "") {
Connection.Instance.PreviousMoves.Add((username, column)); showPopupMessage("Draw!");
} else {
showPopupMessage(_winner + " wins!");
}
}
}
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 ObserveWin(string winner) Connection.Instance.PreviousMoves.Add((username, column));
{ }
PopupMessage(winner + " wins!");
private void showPopupMessage(string message) {
var popup = new Popup();
popup.AlwaysOnTop = true;
popup.Size = new Vector2I(200, 100);
popup.Theme = GD.Load<Theme>("res://assets/theme.tres");
var text = new Label();
text.Text = message;
var sfx = new AudioStreamPlayer();
sfx.Stream = endingSfx;
sfx.VolumeDb = -2;
popup.AddChild(sfx);
popup.AddChild(text);
text.GrowHorizontal = Control.GrowDirection.Both;
text.GrowVertical = Control.GrowDirection.Both;
text.HorizontalAlignment = HorizontalAlignment.Center;
text.VerticalAlignment = VerticalAlignment.Center;
text.AnchorsPreset = (int)Control.LayoutPreset.FullRect;
popup.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
popup.PopupHide += () => popup.QueueFree();
GetTree().Root.AddChild(popup);
popup.PopupCentered();
sfx.Play();
popup.Show();
transitionToBracket();
}
private void transitionToBracket() { GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH); }
/*
* Determines if the column can have a new chip placed
* Will return -1 if invalid
* or
* Will return row of board in which chip can be placed
*/
private int canPlaceOnCol(int col) {
if (col < 0 || col > 6) // Col out of range
return -1;
return getNextAvailRow(col);
}
private int getNextAvailRow(int col) {
for (int i = chips.GetLength(0) - 1; i >= 0; i--) {
// Start at bottom
if (chips[i, col] == null)
return i;
} }
private void ObserveDraw() return -1;
{ }
PopupMessage("Draw!");
private void spawnRed(int col) {
int row = canPlaceOnCol(col);
if (row == -1) {
GD.Print("Invalid Placement!");
return;
} }
private void ObserveTerminated() RigidBody2D newNode = redChip.Instantiate<RigidBody2D>();
{ AddChild(newNode);
PopupMessage("Match Terminated"); newNode.Position = new Vector2(CHIP_SCALE * (CHIP_X_OFF + (CHIP_SIZE + CHIP_PADDING) * col),
-(CHIP_SIZE + CHIP_PADDING) * 7);
chips[row, col] = newNode;
}
private void spawnYellow(int col) {
int row = canPlaceOnCol(col);
if (row == -1) {
GD.Print("Invalid Placement!");
return;
} }
private void PopupMessage(string message) RigidBody2D newNode = ylwChip.Instantiate<RigidBody2D>();
{ AddChild(newNode);
var popup = new Popup(); newNode.Position = new Vector2(CHIP_SCALE * (CHIP_X_OFF + (CHIP_SIZE + CHIP_PADDING) * col),
popup.AlwaysOnTop = true; -(CHIP_SIZE + CHIP_PADDING) * 7);
popup.PopupCentered();
popup.Size = new Vector2I(200, 100);
var text = new Label();
text.Text = message;
popup.AddChild(text);
text.GrowHorizontal = Control.GrowDirection.Both;
text.GrowVertical = Control.GrowDirection.Both;
text.HorizontalAlignment = HorizontalAlignment.Center;
text.VerticalAlignment = VerticalAlignment.Center;
text.AnchorsPreset = (int) Control.LayoutPreset.FullRect;
popup.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
GetTree().Root.AddChild(popup);
popup.Show();
TransitionToBracket();
}
private void ShowTournamentScoreboard(List<(string, int)> playerScoreboard) chips[row, col] = newNode;
{ }
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(); private void setPlayerCardScale(float x, Node2D playerCard) {
tree.HideRoot = true; Sprite2D cardCenter = playerCard.GetNode<Sprite2D>("Center");
tree.Columns = 2; Sprite2D cardRight = playerCard.GetNode<Sprite2D>("Right");
tree.ColumnTitlesVisible = true;
tree.SetColumnTitle(0, "Player");
tree.SetColumnTitle(1, "Score");
var root = tree.CreateItem();
foreach ((string, int) entry in playerScoreboard) float offX = 16 * x / 2;
{ cardCenter.Scale = new Vector2(x, 2);
var item = tree.CreateItem(root); cardCenter.Position = new Vector2(CARD_CENTER_X_DEFAULT + offX, cardCenter.Position.Y);
item.SetText(0, entry.Item1);
item.SetText(1, entry.Item2.ToString());
}
scoreboardWindow.AddChild(tree); cardRight.Position =
new Vector2(CARD_CENTER_X_DEFAULT + offX * 2 + 8,
GetTree().Root.AddChild(scoreboardWindow); cardRight.Position.Y); // 8 is a magic number (im too lazy) for the size of the card edge multipled by 2
} }
private void TransitionToBracket()
{
GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH);
}
/*
* Determines if the column can have a new chip placed
* Will return -1 if invalid
* or
* Will return row of board in which chip can be placed
*/
public int canPlaceOnCol(int col) {
if(col < 0 || col > 6) // Col out of range
return -1;
return getNextAvailRow(col);
}
public int getNextAvailRow(int col) {
for(int i = chips.GetLength(0) - 1; i >= 0; i--) { // Start at bottom
if(chips[i, col] == null)
return i;
}
return -1;
}
private void spawnRed(int col) {
int row = canPlaceOnCol(col);
if(row == -1) {
GD.Print("Invalid Placement!");
return;
}
RigidBody2D newNode = redChip.Instantiate<RigidBody2D>();
AddChild(newNode);
newNode.Position = new Vector2(CHIP_SCALE * (CHIP_X_OFF + (CHIP_SIZE + CHIP_PADDING) * col), -(CHIP_SIZE + CHIP_PADDING) * 7);
chips[row, col] = newNode;
}
private void spawnYellow(int col) {
int row = canPlaceOnCol(col);
if(row == -1) {
GD.Print("Invalid Placement!");
return;
}
RigidBody2D newNode = ylwChip.Instantiate<RigidBody2D>();
AddChild(newNode);
newNode.Position = new Vector2(CHIP_SCALE * (CHIP_X_OFF + (CHIP_SIZE + CHIP_PADDING) * col), -(CHIP_SIZE + CHIP_PADDING) * 7);
chips[row, col] = newNode;
}
private void setPlayerCardScale(float x, Node2D playerCard) {
Sprite2D cardCenter = playerCard.GetNode<Sprite2D>("Center");
Sprite2D cardRight = playerCard.GetNode<Sprite2D>("Right");
float offX = 16 * x / 2;
cardCenter.Scale = new Vector2(x, 2);
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
}
} }
enum Direction enum Direction {
{ UP,
UP, UP_RIGHT,
UP_RIGHT, RIGHT,
RIGHT, DOWN_RIGHT,
DOWN_RIGHT, DOWN,
DOWN, DOWN_LEFT,
DOWN_LEFT, LEFT,
LEFT, UP_LEFT
UP_LEFT
} }

View File

@@ -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() {
Connection.Instance.OnUpdatedPlayers -= updatePlayers;
Connection.Instance.OnUpdatedMatches -= updateMatches;
Connection.Instance.OnWatchGameAck -= transitionToBoard;
}
private void updatePlayers(List<PlayerData> newPlayerList) {
Players.Clear();
playerList = newPlayerList;
playerList.Sort((a, b) => a.Username.CompareTo(b.Username));
var root = Players.CreateItem();
for (int i = 0; i < playerList.Count; i++) {
var item = Players.CreateItem(root);
item.SetText(0, newPlayerList[i].Username);
item.SetText(1, newPlayerList[i].IsReady ? "Yes" : "No");
item.SetText(2, newPlayerList[i].IsPlaying ? "Yes" : "No");
if (Connection.Instance.IsAdmin) {
item.AddButton(0, TerminateKickButton, i, false, "Kick");
}
} }
}
public override void _ExitTree() private void updateMatches(List<MatchData> newMatchList) {
{ Matches.Clear();
Connection.Instance.OnUpdatedPlayers -= UpdatePlayers; matchList = newMatchList;
Connection.Instance.OnUpdatedMatches -= UpdateMatches; var root = Matches.CreateItem();
Connection.Instance.OnWatchGameAck -= TransitionToBoard; for (int i = 0; i < newMatchList.Count; i++) {
var item = Matches.CreateItem(root);
item.SetText(0, newMatchList[i].matchId.ToString());
item.SetText(1, newMatchList[i].player1);
item.SetText(2, newMatchList[i].player2);
item.AddButton(0, WatchButton, i, false, "Watch");
if (Connection.Instance.IsAdmin) {
item.AddButton(0, TerminateKickButton, 128 + i, false, "Terminate");
}
} }
}
private void UpdatePlayers(List<PlayerData> playerList) private void watchGame(TreeItem item, long column, long id, long mouseButtonIndex) {
{ if (mouseButtonIndex == 1 && column == 0 && id < 128) {
Players.Clear(); Connection.Instance.SendWatchGame(matchList[(int)id].matchId);
_playerList = playerList;
var root = Players.CreateItem();
for (int i = 0; i < _playerList.Count; i++)
{
var item = Players.CreateItem(root);
item.SetText(0, playerList[i].username);
item.SetText(1, playerList[i].isReady ? "Yes" : "No");
item.SetText(2, playerList[i].isPlaying ? "Yes" : "No");
if (Connection.Instance.IsAdmin)
{
item.AddButton(0, TerminateKickButton, i, false, "Kick"); // TODO
}
}
} }
}
private void UpdateMatches(List<MatchData> matchList) private void terminateGame(TreeItem item, long column, long id, long mouseButtonIndex) {
{ if (mouseButtonIndex == 1 && column == 0 && id - 128 >= 0 && matchList[(int)id - 128] != null) {
Matches.Clear(); Connection.Instance.TerminateGame(matchList[(int)id - 128].matchId);
_matchList = matchList;
var root = Matches.CreateItem();
for (int i = 0; i < matchList.Count; i++)
{
var item = Matches.CreateItem(root);
item.SetText(0, matchList[i].matchId.ToString());
item.SetText(1, matchList[i].player1);
item.SetText(2, matchList[i].player2);
item.AddButton(0, WatchButton, item.GetButtonCount(0), false, "Watch");
if (Connection.Instance.IsAdmin)
{
item.AddButton(0, TerminateKickButton, item.GetButtonCount(0), false, "Terminate");
}
}
} }
}
private void WatchGame(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) Connection.Instance.KickPlayer(playerList[(int)id].Username);
{
Connection.Instance.SendWatchGame(_matchList[(int) id].matchId);
}
} }
}
private void KickPlayer(TreeItem item, long column, long id, long mouseButtonIndex) private void transitionToBoard() { GetTree().ChangeSceneToFile(BOARD_SCENE_PATH); }
{
if (mouseButtonIndex == 1 && column == 0)
{
Connection.Instance.KickPlayer(_playerList[(int) id].username);
}
}
private void TerminateGame(TreeItem item, long column, long id, long mouseButtonIndex)
{
if (mouseButtonIndex == 1 && column == 0 && id - 1 > 0 && _matchList[(int) id - 1] != null)
{
Connection.Instance.TerminateGame(_matchList[(int) id - 1].matchId);
}
}
private void TransitionToBoard()
{
GetTree().ChangeSceneToFile(BOARD_SCENE_PATH);
}
} }

View 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;
}
}
}

View File

@@ -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
{
ErrorLabel.Text = "Couldn't connect to server!";
}
base._Pressed();
} }
if (Connection.Instance.LastError.Length > 0) {
ErrorLabel.Text = Connection.Instance.LastError;
}
AddressField.GuiInput += e =>
{
if (AddressField.HasFocus() && e is InputEventKey inputEventKey && inputEventKey.IsPressed()) {
if (inputEventKey.KeyLabel == Key.Enter) {
Connection.Instance.Connect(AddressField.Text);
GetViewport().SetInputAsHandled();
}
if (inputEventKey.KeyLabel == Key.Space) {
GetViewport().SetInputAsHandled();
}
}
};
}
public override void _ExitTree() {
Connection.Instance.OnWsConnectionSuccess -= OnConnectionSuccess;
Connection.Instance.OnWsConnectionFailed -= OnConnectionFailed;
}
public override void _Pressed() { 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;
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,11 @@
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; }
}
} }

View File

@@ -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; }
}
} }

View File

@@ -0,0 +1,4 @@
public enum TournamentType {
None,
RoundRobin
}

View File

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

View File

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

View File

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

13
scripts/chip_sfx.gd Normal file
View File

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

View File

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

View File

@@ -1,12 +0,0 @@
extends RigidBody2D
var color_ = "red";
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
pass

10
scripts/thinking.gd Normal file
View 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
View File

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

View File

@@ -1,12 +0,0 @@
extends RigidBody2D
var color_ = "yellow";
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
pass

View File

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