From 544e0b22c5f6d17da499be1012dd3c14971dbdc4 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 15 May 2024 19:42:18 +0200 Subject: [PATCH 1/2] Make games rejoinable 1. Disconnect websocket connection before connecting 2. store playerInfo when hosting a game to reuse it when rejoining --- lib/api/register.dart | 29 +++++++++++++++++++------ lib/connection/ws_connection.dart | 4 +++- lib/pages/host_game.dart | 7 +++--- lib/pages/lobby_selector.dart | 36 ++++++++++++++++++++----------- lib/utils/config.dart | 2 +- 5 files changed, 54 insertions(+), 24 deletions(-) diff --git a/lib/api/register.dart b/lib/api/register.dart index c3f30fd..927b7c2 100644 --- a/lib/api/register.dart +++ b/lib/api/register.dart @@ -12,6 +12,10 @@ class PlayerInfo { required this.passphrase, }); + factory PlayerInfo.empty() { + return const PlayerInfo(playerID: null, lobbyID: null, passphrase: null); + } + factory PlayerInfo.fromJson(Map json) { final playerid = UuidValue.fromString(json['playerID']); final lobbyid = UuidValue.fromString(json['lobbyID']); @@ -21,11 +25,24 @@ class PlayerInfo { playerID: playerid, lobbyID: lobbyid, passphrase: passphrase); } - Map toJson() => { - 'playerID': playerID, - 'lobbyID': lobbyID, - 'passphrase': passphrase, - }; + Map toJson() { + String? pid; + String? lid; + + if (playerID != null) { + pid = playerID.toString(); + } + + if (lobbyID != null) { + lid = lobbyID.toString(); + } + + return { + 'playerID': pid, + 'lobbyID': lid, + 'passphrase': passphrase, + }; + } void store() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); @@ -42,7 +59,7 @@ class PlayerInfo { await prefs.setBool("contains", false); } - Future get() async { + static Future get() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); var contains = prefs.getBool("contains"); var playerID = prefs.getString("playerID"); diff --git a/lib/connection/ws_connection.dart b/lib/connection/ws_connection.dart index b001179..3c43b57 100644 --- a/lib/connection/ws_connection.dart +++ b/lib/connection/ws_connection.dart @@ -16,7 +16,6 @@ import 'package:web_socket_channel/web_socket_channel.dart'; class ServerConnection { WebSocketChannel? channel; - late bool wasConnected = false; Stream broadcast = const Stream.empty(); static final ServerConnection _instance = ServerConnection._internal(); @@ -42,6 +41,7 @@ class ServerConnection { } void connect(String playerID, lobbyID, String? passphrase) { + disconnectExistingConnection(); channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL())); send( @@ -62,6 +62,8 @@ class ServerConnection { void disconnectExistingConnection() { if (channel == null) return; channel!.sink.close(); + + broadcast = const Stream.empty(); } void handleIncomingData(dynamic data) { diff --git a/lib/pages/host_game.dart b/lib/pages/host_game.dart index 1f59fb8..757b171 100644 --- a/lib/pages/host_game.dart +++ b/lib/pages/host_game.dart @@ -27,9 +27,6 @@ class _HostGameWidgetState extends State { @override void initState() { registerResponse = hostPrivateGame(); - registerResponse.then((value) { - value?.store(); - }); connectToWebsocket(registerResponse); super.initState(); } @@ -70,6 +67,7 @@ class _HostGameWidgetState extends State { listener: (context, state) { // We wait for our opponent to connect if (state.opponentConnected) { + snapshot.data?.store(); context.pushReplacement('/game', extra: chessGameArgs); } }, @@ -135,7 +133,8 @@ class _HostGameWidgetState extends State { if (response.statusCode == 200) { log(response.body); - return PlayerInfo.fromJson(jsonDecode(response.body)); + var info = PlayerInfo.fromJson(jsonDecode(response.body)); + return info; } return null; } diff --git a/lib/pages/lobby_selector.dart b/lib/pages/lobby_selector.dart index a0232a0..5c3385b 100644 --- a/lib/pages/lobby_selector.dart +++ b/lib/pages/lobby_selector.dart @@ -6,10 +6,10 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:mchess/api/register.dart'; +import 'package:mchess/connection/ws_connection.dart'; import 'package:mchess/connection_cubit/connection_cubit.dart'; import 'package:mchess/pages/chess_game.dart'; import 'package:mchess/utils/config.dart' as config; -import 'package:shared_preferences/shared_preferences.dart'; class LobbySelector extends StatefulWidget { const LobbySelector({super.key}); @@ -25,13 +25,6 @@ class _LobbySelectorState extends State { @override Widget build(BuildContext context) { - SharedPreferences.getInstance().then((prefs) { - final playerID = prefs.getString("playerID"); - final lobbyID = prefs.getString("lobbyID"); - final passphrase = prefs.getString("passphrase"); - log("lobbyID: $lobbyID and playerID: $playerID and passphrase: $passphrase"); - }); - return Scaffold( body: Center( child: Column( @@ -57,6 +50,9 @@ class _LobbySelectorState extends State { } Future buildJoinOrHostDialog(BuildContext context) { + //TODO: find a better place to disconnect old websocket connection + ServerConnection.getInstance().disconnectExistingConnection(); + return showDialog( context: context, builder: (BuildContext context) { @@ -170,13 +166,28 @@ class _LobbySelectorState extends State { Future joinPrivateGame(BuildContext context) async { http.Response response; - // server expects us to send the passphrase - var info = PlayerInfo( - playerID: null, lobbyID: null, passphrase: phraseController.text); + var existingInfo = await PlayerInfo.get(); + + log("lobbyID: ${existingInfo?.lobbyID} and playerID: ${existingInfo?.playerID} and passphrase: ${existingInfo?.passphrase}"); + + PlayerInfo info; + if (existingInfo?.passphrase == phraseController.text) { + // We have player info for this exact passphrase + info = PlayerInfo( + playerID: existingInfo?.playerID, + lobbyID: existingInfo?.lobbyID, + passphrase: phraseController.text); + } else { + info = PlayerInfo( + playerID: null, lobbyID: null, passphrase: phraseController.text); + } + + var decodedInfo = jsonEncode(info); + log("decodedInfo: $decodedInfo"); try { response = await http.post(Uri.parse(config.getJoinURL()), - body: jsonEncode(info), headers: {"Accept": "application/json"}); + body: decodedInfo, headers: {"Accept": "application/json"}); } catch (e) { log(e.toString()); @@ -206,6 +217,7 @@ class _LobbySelectorState extends State { if (response.statusCode == HttpStatus.ok) { var info = PlayerInfo.fromJson(jsonDecode(response.body)); + info.store(); log('Player info received from server: '); log('lobbyID: ${info.lobbyID}'); log('playerID: ${info.playerID}'); diff --git a/lib/utils/config.dart b/lib/utils/config.dart index 863dedb..c110b8a 100644 --- a/lib/utils/config.dart +++ b/lib/utils/config.dart @@ -1,7 +1,7 @@ const prodURL = 'chess.sw-gross.de:9999'; const debugURL = 'localhost:8080'; -const useDbgUrl = false; +const useDbgUrl = true; String getHostURL() { var prot = 'https'; From 2bed5409ef9bc39611af2416e10c8569b61794c6 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 15 May 2024 21:15:20 +0200 Subject: [PATCH 2/2] flutter pub upgrade --- pubspec.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 2c988fe..01a31df 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -148,10 +148,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "9e0f7d1a3e7dc5010903e330fbc5497872c4c3cf6626381d69083cc1d5113c1e" + sha256: "7685acd06244ba4be60f455c5cafe5790c63dc91fc03f7385b1e922a6b85b17c" url: "https://pub.dev" source: hosted - version: "14.0.2" + version: "14.1.1" http: dependency: "direct main" description: @@ -529,10 +529,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.1" xdg_directories: dependency: transitive description: @@ -550,5 +550,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.4.0 <4.0.0" flutter: ">=3.19.0"