Make games rejoinable

1. Disconnect websocket connection before connecting
2. store playerInfo when hosting a game to reuse it when rejoining
This commit is contained in:
Marco 2024-05-15 19:42:18 +02:00
parent 618102dd67
commit 544e0b22c5
5 changed files with 54 additions and 24 deletions

View File

@ -12,6 +12,10 @@ class PlayerInfo {
required this.passphrase, required this.passphrase,
}); });
factory PlayerInfo.empty() {
return const PlayerInfo(playerID: null, lobbyID: null, passphrase: null);
}
factory PlayerInfo.fromJson(Map<String, dynamic> json) { factory PlayerInfo.fromJson(Map<String, dynamic> json) {
final playerid = UuidValue.fromString(json['playerID']); final playerid = UuidValue.fromString(json['playerID']);
final lobbyid = UuidValue.fromString(json['lobbyID']); final lobbyid = UuidValue.fromString(json['lobbyID']);
@ -21,11 +25,24 @@ class PlayerInfo {
playerID: playerid, lobbyID: lobbyid, passphrase: passphrase); playerID: playerid, lobbyID: lobbyid, passphrase: passphrase);
} }
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() {
'playerID': playerID, String? pid;
'lobbyID': lobbyID, String? lid;
if (playerID != null) {
pid = playerID.toString();
}
if (lobbyID != null) {
lid = lobbyID.toString();
}
return {
'playerID': pid,
'lobbyID': lid,
'passphrase': passphrase, 'passphrase': passphrase,
}; };
}
void store() async { void store() async {
final SharedPreferences prefs = await SharedPreferences.getInstance(); final SharedPreferences prefs = await SharedPreferences.getInstance();
@ -42,7 +59,7 @@ class PlayerInfo {
await prefs.setBool("contains", false); await prefs.setBool("contains", false);
} }
Future<PlayerInfo?> get() async { static Future<PlayerInfo?> get() async {
final SharedPreferences prefs = await SharedPreferences.getInstance(); final SharedPreferences prefs = await SharedPreferences.getInstance();
var contains = prefs.getBool("contains"); var contains = prefs.getBool("contains");
var playerID = prefs.getString("playerID"); var playerID = prefs.getString("playerID");

View File

@ -16,7 +16,6 @@ import 'package:web_socket_channel/web_socket_channel.dart';
class ServerConnection { class ServerConnection {
WebSocketChannel? channel; WebSocketChannel? channel;
late bool wasConnected = false;
Stream broadcast = const Stream.empty(); Stream broadcast = const Stream.empty();
static final ServerConnection _instance = ServerConnection._internal(); static final ServerConnection _instance = ServerConnection._internal();
@ -42,6 +41,7 @@ class ServerConnection {
} }
void connect(String playerID, lobbyID, String? passphrase) { void connect(String playerID, lobbyID, String? passphrase) {
disconnectExistingConnection();
channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL())); channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL()));
send( send(
@ -62,6 +62,8 @@ class ServerConnection {
void disconnectExistingConnection() { void disconnectExistingConnection() {
if (channel == null) return; if (channel == null) return;
channel!.sink.close(); channel!.sink.close();
broadcast = const Stream.empty();
} }
void handleIncomingData(dynamic data) { void handleIncomingData(dynamic data) {

View File

@ -27,9 +27,6 @@ class _HostGameWidgetState extends State<HostGameWidget> {
@override @override
void initState() { void initState() {
registerResponse = hostPrivateGame(); registerResponse = hostPrivateGame();
registerResponse.then((value) {
value?.store();
});
connectToWebsocket(registerResponse); connectToWebsocket(registerResponse);
super.initState(); super.initState();
} }
@ -70,6 +67,7 @@ class _HostGameWidgetState extends State<HostGameWidget> {
listener: (context, state) { listener: (context, state) {
// We wait for our opponent to connect // We wait for our opponent to connect
if (state.opponentConnected) { if (state.opponentConnected) {
snapshot.data?.store();
context.pushReplacement('/game', extra: chessGameArgs); context.pushReplacement('/game', extra: chessGameArgs);
} }
}, },
@ -135,7 +133,8 @@ class _HostGameWidgetState extends State<HostGameWidget> {
if (response.statusCode == 200) { if (response.statusCode == 200) {
log(response.body); log(response.body);
return PlayerInfo.fromJson(jsonDecode(response.body)); var info = PlayerInfo.fromJson(jsonDecode(response.body));
return info;
} }
return null; return null;
} }

View File

@ -6,10 +6,10 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:mchess/api/register.dart'; import 'package:mchess/api/register.dart';
import 'package:mchess/connection/ws_connection.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart'; import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/pages/chess_game.dart'; import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/utils/config.dart' as config; import 'package:mchess/utils/config.dart' as config;
import 'package:shared_preferences/shared_preferences.dart';
class LobbySelector extends StatefulWidget { class LobbySelector extends StatefulWidget {
const LobbySelector({super.key}); const LobbySelector({super.key});
@ -25,13 +25,6 @@ class _LobbySelectorState extends State<LobbySelector> {
@override @override
Widget build(BuildContext context) { 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( return Scaffold(
body: Center( body: Center(
child: Column( child: Column(
@ -57,6 +50,9 @@ class _LobbySelectorState extends State<LobbySelector> {
} }
Future<void> buildJoinOrHostDialog(BuildContext context) { Future<void> buildJoinOrHostDialog(BuildContext context) {
//TODO: find a better place to disconnect old websocket connection
ServerConnection.getInstance().disconnectExistingConnection();
return showDialog<void>( return showDialog<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -170,13 +166,28 @@ class _LobbySelectorState extends State<LobbySelector> {
Future<PlayerInfo?> joinPrivateGame(BuildContext context) async { Future<PlayerInfo?> joinPrivateGame(BuildContext context) async {
http.Response response; http.Response response;
// server expects us to send the passphrase var existingInfo = await PlayerInfo.get();
var info = PlayerInfo(
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); playerID: null, lobbyID: null, passphrase: phraseController.text);
}
var decodedInfo = jsonEncode(info);
log("decodedInfo: $decodedInfo");
try { try {
response = await http.post(Uri.parse(config.getJoinURL()), response = await http.post(Uri.parse(config.getJoinURL()),
body: jsonEncode(info), headers: {"Accept": "application/json"}); body: decodedInfo, headers: {"Accept": "application/json"});
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());
@ -206,6 +217,7 @@ class _LobbySelectorState extends State<LobbySelector> {
if (response.statusCode == HttpStatus.ok) { if (response.statusCode == HttpStatus.ok) {
var info = PlayerInfo.fromJson(jsonDecode(response.body)); var info = PlayerInfo.fromJson(jsonDecode(response.body));
info.store();
log('Player info received from server: '); log('Player info received from server: ');
log('lobbyID: ${info.lobbyID}'); log('lobbyID: ${info.lobbyID}');
log('playerID: ${info.playerID}'); log('playerID: ${info.playerID}');

View File

@ -1,7 +1,7 @@
const prodURL = 'chess.sw-gross.de:9999'; const prodURL = 'chess.sw-gross.de:9999';
const debugURL = 'localhost:8080'; const debugURL = 'localhost:8080';
const useDbgUrl = false; const useDbgUrl = true;
String getHostURL() { String getHostURL() {
var prot = 'https'; var prot = 'https';