feat: theming pass, reworked connection flow...

- bracket view theming (pending rework)
- some light refactoring
- TOURNAMENT:CANCEL command (pending server side implementation)
- logic for waiting for last moves in games to wait for chip to drop
- transition back to main menu on disconnects, disconnect tolerance
This commit is contained in:
2025-12-07 01:24:13 -05:00
Unverified
parent 7ee74478c3
commit 95c3cef1be
12 changed files with 344 additions and 151 deletions

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,8 +1,9 @@
[gd_scene load_steps=16 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="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"]
@@ -36,6 +37,7 @@ 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") endingSfx = ExtResource("2_kseed")
theme = ExtResource("3_3louw")
[node name="Floor Collider" type="StaticBody2D" parent="."] [node name="Floor Collider" type="StaticBody2D" parent="."]
position = Vector2(0, 200) position = Vector2(0, 200)

View File

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

View File

@@ -1,10 +1,33 @@
[gd_scene load_steps=3 format=3 uid="uid://cr8fi0e4r88s8"] [gd_scene load_steps=5 format=3 uid="uid://cr8fi0e4r88s8"]
[ext_resource type="PackedScene" uid="uid://cct663hb47yka" path="res://scenes/create_join_room.tscn" id="1_yqjtg"] [ext_resource type="PackedScene" uid="uid://cct663hb47yka" path="res://scenes/create_join_room.tscn" id="1_yqjtg"]
[ext_resource type="PackedScene" uid="uid://m542qwlp7hl7" path="res://scenes/board_screen.tscn" id="2_lnu2h"] [ext_resource type="PackedScene" uid="uid://m542qwlp7hl7" path="res://scenes/board_screen.tscn" id="2_lnu2h"]
[sub_resource type="Gradient" id="Gradient_wu84c"]
colors = PackedColorArray(0, 0.07058824, 0.101960786, 1, 0.39215687, 0.39215687, 0.39215687, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_yqjtg"]
gradient = SubResource("Gradient_wu84c")
width = 1024
height = 1024
fill_from = Vector2(0.5, 1)
fill_to = Vector2(0.5, 0)
metadata/_snap_enabled = true
[node name="Game" type="Node2D"] [node name="Game" type="Node2D"]
[node name="Background" type="TextureRect" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -659.0
offset_top = -558.0
offset_right = 619.0
offset_bottom = 466.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,29 +1,31 @@
using Godot; using Godot;
using System;
using System.Collections.Generic;
public partial class AdminControls : HBoxContainer public partial class AdminControls : HBoxContainer
{ {
[Export] public Button BecomeAdmin; [Export] public Button BecomeAdmin;
[Export] public Button StartTournament; [Export] public Button StartTournament;
[Export] public Button CancelTournament;
[Export] public Label Label; [Export] public Label Label;
[Export] public Slider Timeout; [Export] public Slider Timeout;
public override void _Ready() public override void _Ready()
{ {
Connection.Instance.OnBecomeAdmin += OnBecomeAdmin; Connection.Instance.OnBecomeAdmin += OnBecomeAdmin;
Connection.Instance.OnTournamentEnd += OnEndTournament; Connection.Instance.OnTournamentEnd += _ => StartTournament.Show();
Connection.Instance.OnStartTournamentAck += () => StartTournament.Hide();
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) if (!Connection.Instance.IsAdmin)
{ {
StartTournament.Hide();
CancelTournament.Hide();
Label.Hide(); Label.Hide();
Timeout.Hide(); Timeout.Hide();
}
else if (Connection.Instance.ActiveTournament)
{
StartTournament.Hide();
} }
Timeout.Value = Connection.Instance.CurrentWaitTimeout; Timeout.Value = Connection.Instance.CurrentWaitTimeout;
@@ -51,23 +53,12 @@ public partial class AdminControls : HBoxContainer
public override void _ExitTree() public override void _ExitTree()
{ {
Connection.Instance.OnBecomeAdmin -= OnBecomeAdmin; Connection.Instance.OnBecomeAdmin -= OnBecomeAdmin;
Connection.Instance.OnTournamentEnd -= OnEndTournament;
}
private void StartTournamentCommand()
{
Connection.Instance.StartTournament();
}
private void OnEndTournament(List<(string, int)> playerScoreboard)
{
StartTournament.Show();
ShowTournamentScoreboard(playerScoreboard);
} }
private void ShowAuthPopup() private void ShowAuthPopup()
{ {
var authWindow = new Window(); var authWindow = new Window();
authWindow.Theme = GD.Load<Theme>("res://assets/theme.tres");
authWindow.AlwaysOnTop = true; authWindow.AlwaysOnTop = true;
authWindow.MaximizeDisabled = true; authWindow.MaximizeDisabled = true;
authWindow.Unresizable = true; authWindow.Unresizable = true;
@@ -106,39 +97,6 @@ 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() private void OnBecomeAdmin()
{ {
BecomeAdmin.Hide(); BecomeAdmin.Hide();
@@ -146,6 +104,10 @@ public partial class AdminControls : HBoxContainer
{ {
StartTournament.Show(); StartTournament.Show();
} }
else
{
CancelTournament.Show();
}
Label.Show(); Label.Show();
Timeout.Show(); Timeout.Show();
} }

View File

@@ -1,5 +1,4 @@
using Godot; using Godot;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -7,6 +6,7 @@ public partial class BoardScreen : Node2D
{ {
[Export] private AudioStream endingSfx; [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";
@@ -30,6 +30,10 @@ public partial class BoardScreen : Node2D
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
@@ -62,11 +66,12 @@ public partial class BoardScreen : Node2D
player2Card.GetNode<Label>("Status").Hide(); player2Card.GetNode<Label>("Status").Hide();
} }
Connection.Instance.OnObserveWin += ObserveWin; Connection.Instance.OnObserveWin += winner => { _lastMove = true; _winner = winner; };
Connection.Instance.OnObserveDraw += ObserveDraw; Connection.Instance.OnObserveDraw += () => _lastMove = true;
Connection.Instance.OnObserveTerminated += ObserveTerminated; Connection.Instance.OnObserveTerminated += () => PopupMessage("Match Terminated");
Connection.Instance.OnObserveMove += ObserveMove; Connection.Instance.OnObserveMove += ObserveMove;
Connection.Instance.OnTournamentEnd += ShowTournamentScoreboard; Connection.Instance.OnTournamentEnd += ShowTournamentScoreboard;
Connection.Instance.OnWSDisconnect += () => GetTree().ChangeSceneToFile("res://scenes/main_menu.tscn");
} }
public override void _Process(double delta) public override void _Process(double delta)
@@ -90,13 +95,27 @@ public partial class BoardScreen : Node2D
{ {
currentTimeout -= delta; currentTimeout -= delta;
} }
if (_lastMove)
{
_lastMoveTimer -= (float) delta;
}
if (_lastMoveTimer <= 0.0f)
{
if (_winner == "")
{
PopupMessage("Draw!");
}
else
{
PopupMessage(_winner + " wins!");
}
}
} }
public override void _ExitTree() public override void _ExitTree()
{ {
Connection.Instance.OnObserveWin -= ObserveWin;
Connection.Instance.OnObserveDraw -= ObserveDraw;
Connection.Instance.OnObserveTerminated -= ObserveTerminated;
Connection.Instance.OnObserveMove -= ObserveMove; Connection.Instance.OnObserveMove -= ObserveMove;
Connection.Instance.OnTournamentEnd -= ShowTournamentScoreboard; Connection.Instance.OnTournamentEnd -= ShowTournamentScoreboard;
} }
@@ -116,27 +135,13 @@ public partial class BoardScreen : Node2D
Connection.Instance.PreviousMoves.Add((username, column)); Connection.Instance.PreviousMoves.Add((username, column));
} }
private void ObserveWin(string winner)
{
PopupMessage(winner + " wins!");
}
private void ObserveDraw()
{
PopupMessage("Draw!");
}
private void ObserveTerminated()
{
PopupMessage("Match Terminated");
}
private void PopupMessage(string message) private void PopupMessage(string message)
{ {
var popup = new Popup(); var popup = new Popup();
popup.AlwaysOnTop = true; popup.AlwaysOnTop = true;
popup.PopupCentered(); popup.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(); var sfx = new AudioStreamPlayer();

View File

@@ -1,5 +1,4 @@
using Godot; using Godot;
using System;
using System.Collections.Generic; using System.Collections.Generic;
public partial class BracketScene : Control public partial class BracketScene : Control
@@ -32,6 +31,8 @@ public partial class BracketScene : Control
Connection.Instance.OnUpdatedPlayers += UpdatePlayers; Connection.Instance.OnUpdatedPlayers += UpdatePlayers;
Connection.Instance.OnUpdatedMatches += UpdateMatches; Connection.Instance.OnUpdatedMatches += UpdateMatches;
Connection.Instance.OnWatchGameAck += TransitionToBoard; Connection.Instance.OnWatchGameAck += TransitionToBoard;
Connection.Instance.OnTournamentEnd += ShowTournamentScoreboard;
Connection.Instance.OnWSDisconnect += () => GetTree().ChangeSceneToFile("res://scenes/main_menu.tscn");
} }
public override void _ExitTree() public override void _ExitTree()
@@ -39,6 +40,7 @@ public partial class BracketScene : Control
Connection.Instance.OnUpdatedPlayers -= UpdatePlayers; Connection.Instance.OnUpdatedPlayers -= UpdatePlayers;
Connection.Instance.OnUpdatedMatches -= UpdateMatches; Connection.Instance.OnUpdatedMatches -= UpdateMatches;
Connection.Instance.OnWatchGameAck -= TransitionToBoard; Connection.Instance.OnWatchGameAck -= TransitionToBoard;
Connection.Instance.OnTournamentEnd -= ShowTournamentScoreboard;
} }
private void UpdatePlayers(List<PlayerData> playerList) private void UpdatePlayers(List<PlayerData> playerList)
@@ -106,4 +108,39 @@ public partial class BracketScene : Control
{ {
GetTree().ChangeSceneToFile(BOARD_SCENE_PATH); GetTree().ChangeSceneToFile(BOARD_SCENE_PATH);
} }
private void ShowTournamentScoreboard(List<(string, int)> playerScoreboard)
{
var scoreboardWindow = new Window();
scoreboardWindow.Theme = GD.Load<Theme>("res://assets/theme.tres");
scoreboardWindow.AlwaysOnTop = true;
scoreboardWindow.MaximizeDisabled = true;
scoreboardWindow.Unresizable = true;
scoreboardWindow.InitialPosition = Window.WindowInitialPosition.CenterMainWindowScreen;
scoreboardWindow.Size = new Vector2I(256, 512);
scoreboardWindow.CloseRequested += () =>
{
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.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);
}
} }

View File

@@ -1,23 +1,19 @@
using Godot; using Godot;
using System;
public partial class ConnectButtonUI : Button public partial class ConnectButtonUI : Button
{ {
[Export] public TextEdit AddressUi; [Export] public TextEdit AddressField;
[Export] public Label ErrorLabel; [Export] public Label ErrorLabel;
private const string BRACKET_SCENE_PATH = "res://scenes/bracket_view.tscn"; private const string BRACKET_SCENE_PATH = "res://scenes/bracket_view.tscn";
public override void _Ready()
{
Connection.Instance.OnWSConnectionSuccess += () => GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH);
Connection.Instance.OnWSConnectionFailed += () => ErrorLabel.Text = "Couldn't connect to server! " + Connection.Instance.LastError;
}
public override void _Pressed() public override void _Pressed()
{ {
if (Connection.Instance.Connect(AddressUi.Text)) Connection.Instance.Connect(AddressField.Text);
{
GD.Print("Success!");
GetTree().ChangeSceneToFile(BRACKET_SCENE_PATH);
}
else
{
ErrorLabel.Text = "Couldn't connect to server!";
}
base._Pressed();
} }
} }

View File

@@ -9,8 +9,7 @@ public partial class Connection : Node
public static Connection Instance { get; private set; } public static Connection Instance { get; private set; }
private WebSocketPeer _webSocket = new WebSocketPeer(); private WebSocketPeer _webSocket = new ();
private bool _firstConnect = true;
private Thread _gameListThread; private Thread _gameListThread;
private bool _gameListThreadRunning; private bool _gameListThreadRunning;
@@ -33,15 +32,18 @@ public partial class Connection : Node
public event Action OnStartTournamentAck; public event Action OnStartTournamentAck;
public event Action<List<(string, int)>> OnTournamentEnd; public event Action<List<(string, int)>> OnTournamentEnd;
public event Action OnBecomeAdmin; public event Action OnBecomeAdmin;
public event Action OnWSConnectionSuccess;
public event Action OnWSConnectionFailed;
public event Action OnWSDisconnect;
// Already prints to console // Already prints to console
public event Action<string, string> OnError; public event Action<string> OnError;
public bool IsAdmin { get; private set; } public bool IsAdmin { get; private set; }
public bool IsPlayer { get; private set; } public bool IsPlayer { get; private set; }
public bool ActiveTournament { get; private set; } public bool ActiveTournament { get; private set; }
public List<(string, int)> PreviousMoves { get; private set; } = new List<(string, int)>(); public List<(string, int)> PreviousMoves { get; private set; } = [];
public double CurrentWaitTimeout { get; private set; } = 5.0; public double CurrentWaitTimeout { get; private set; } = 5.0;
@@ -49,6 +51,11 @@ public partial class Connection : Node
private bool IsSocketOpen => _webSocket.GetReadyState() == WebSocketPeer.State.Open; private bool IsSocketOpen => _webSocket.GetReadyState() == WebSocketPeer.State.Open;
private bool _connecting = false;
private bool _connected = false;
public String LastError = "";
public override void _Ready() public override void _Ready()
{ {
Instance = this; Instance = this;
@@ -56,37 +63,19 @@ public partial class Connection : Node
_webSocket.HeartbeatInterval = 5.0; _webSocket.HeartbeatInterval = 5.0;
} }
public bool Connect(string address) public void Connect(string address)
{ {
if (_webSocket.GetReadyState() == WebSocketPeer.State.Open) _connecting = true;
if (_connected)
{ {
return false; return;
} }
Error error = _webSocket.ConnectToUrl(address); Error error = _webSocket.ConnectToUrl(address);
if (error != Error.Ok) if (error != Error.Ok)
{ {
return false; LastError = error.ToString();
} }
_webSocket.Poll();
while (_webSocket.GetReadyState() == WebSocketPeer.State.Connecting)
{
_webSocket.Poll();
Thread.Sleep(TimeSpan.FromMilliseconds(5));
}
if (_webSocket.GetReadyState() != WebSocketPeer.State.Open)
{
return false;
}
_webSocket.SetHeartbeatInterval(5.0);
_webSocket.HeartbeatInterval = 5.0;
_firstConnect = false;
StartGameListRefreshLoop();
return true;
} }
public override void _ExitTree() public override void _ExitTree()
@@ -94,24 +83,50 @@ public partial class Connection : Node
StopGameListRefreshLoop(); StopGameListRefreshLoop();
} }
public override void _PhysicsProcess(double delta) public override void _Process(double delta)
{ {
_webSocket.Poll(); _webSocket.Poll();
WebSocketPeer.State state = _webSocket.GetReadyState(); WebSocketPeer.State state = _webSocket.GetReadyState();
if ((state == WebSocketPeer.State.Closed || state == WebSocketPeer.State.Closing) && !_firstConnect) if (state == WebSocketPeer.State.Open)
{
StopGameListRefreshLoop();
GD.PrintErr("Connection lost.");
//GetTree().Quit();
}
if (IsSocketOpen)
{ {
if (_connecting)
{
_connecting = false;
_connected = true;
OnWSConnectionSuccess?.Invoke();
StartGameListRefreshLoop();
}
while (_webSocket.GetAvailablePacketCount() > 0) while (_webSocket.GetAvailablePacketCount() > 0)
{ {
string message = _webSocket.GetPacket().GetStringFromUtf8(); string message = _webSocket.GetPacket().GetStringFromUtf8();
HandleServerMessage(message); HandleServerMessage(message);
} }
}
else if (state == WebSocketPeer.State.Connecting)
{
// Do nothing
}
else if (state == WebSocketPeer.State.Closing)
{
// Do nothing
}
else if (state == WebSocketPeer.State.Closed)
{
if (_connecting)
{
_connecting = false;
OnWSConnectionFailed?.Invoke();
}
else if (_connected)
{
_connected = false;
StopGameListRefreshLoop();
var code = _webSocket.GetCloseCode();
var reason = _webSocket.GetCloseReason();
GD.PrintErr("WebSocket closed with code: " + code + ", reason " + reason + ". Clean: " + (code != -1));
OnWSDisconnect?.Invoke();
}
} }
} }
@@ -220,6 +235,12 @@ public partial class Connection : Node
SendCommand("TOURNAMENT", "START"); SendCommand("TOURNAMENT", "START");
} }
public void CancelTournament()
{
if (!IsAdmin) return;
SendCommand("TOURNAMENT", "CANCEL");
}
public void TerminateGame(int matchID) public void TerminateGame(int matchID)
{ {
if (!IsAdmin) return; if (!IsAdmin) return;
@@ -317,8 +338,8 @@ public partial class Connection : Node
HandleTournamentMessage(body); HandleTournamentMessage(body);
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}");
@@ -495,19 +516,4 @@ public partial class Connection : Node
} }
} }
} }
private void HandleErrorMessage(string body)
{
if (string.IsNullOrWhiteSpace(body))
{
OnError?.Invoke("UNKNOWN", string.Empty);
return;
}
string[] segments = body.Split(':');
string code = segments.Length > 0 ? segments[0].Trim().ToUpperInvariant() : "UNKNOWN";
string detail = segments.Length > 1 ? string.Join(":", segments, 1, segments.Length - 1).Trim() : string.Empty;
OnError?.Invoke(code, detail);
}
} }