Make many changes

1. A game is only identified by a passphrase (not a lobby id)
2. We can store multiple passphrase/playerID combinations
This commit is contained in:
Marco 2024-05-20 15:34:20 +02:00
parent a10db3e2a2
commit adf8c86692
8 changed files with 68 additions and 130 deletions

View File

@ -3,43 +3,33 @@ import 'package:uuid/uuid.dart';
class GameInfo {
final UuidValue? playerID;
final UuidValue? lobbyID;
final String? passphrase;
const GameInfo({
required this.playerID,
required this.lobbyID,
required this.passphrase,
});
factory GameInfo.empty() {
return const GameInfo(playerID: null, lobbyID: null, passphrase: null);
return const GameInfo(playerID: null, passphrase: null);
}
factory GameInfo.fromJson(Map<String, dynamic> json) {
final playerid = UuidValue.fromString(json['playerID']);
final lobbyid = UuidValue.fromString(json['lobbyID']);
final passphrase = json['passphrase'];
return GameInfo(
playerID: playerid, lobbyID: lobbyid, passphrase: passphrase);
return GameInfo(playerID: playerid, passphrase: passphrase);
}
Map<String, dynamic> toJson() {
String? pid;
String? lid;
if (playerID != null) {
pid = playerID.toString();
}
if (lobbyID != null) {
lid = lobbyID.toString();
}
return {
'playerID': pid,
'lobbyID': lid,
'passphrase': passphrase,
};
}
@ -47,51 +37,29 @@ class GameInfo {
void store() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool("contains", true);
await prefs.setString("playerID", playerID.toString());
await prefs.setString("lobbyID", lobbyID.toString());
await prefs.setString("passphrase", passphrase.toString());
await prefs.setString(passphrase!, playerID.toString());
}
void delete() async {
static Future<GameInfo?> get(String phrase) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
var playerID = prefs.getString(phrase);
await prefs.setBool("contains", false);
}
static Future<GameInfo?> get() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
var contains = prefs.getBool("contains");
var playerID = prefs.getString("playerID");
var lobbyID = prefs.getString("lobbyID");
var passphrase = prefs.getString("passphrase");
if (contains == null ||
!contains ||
playerID == null ||
lobbyID == null ||
passphrase == null) {
return null;
}
if (playerID == null) return null;
return GameInfo(
playerID: UuidValue.fromString(playerID),
lobbyID: UuidValue.fromString(lobbyID),
passphrase: passphrase);
playerID: UuidValue.fromString(playerID!), passphrase: phrase);
}
}
class WebsocketMessageIdentifyPlayer {
final String playerID;
final String lobbyID;
final String? passphrase;
const WebsocketMessageIdentifyPlayer({
required this.playerID,
required this.lobbyID,
required this.passphrase,
});
Map<String, dynamic> toJson() =>
{'lobbyID': lobbyID, 'playerID': playerID, 'passphrase': passphrase};
{'playerID': playerID, 'passphrase': passphrase};
}

View File

@ -40,15 +40,15 @@ class ServerConnection {
channel!.sink.add(message);
}
void connect(String playerID, lobbyID, String? passphrase) {
disconnectExistingConnection();
void connect(String playerID, String? passphrase) {
if (channel != null) return;
channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL()));
send(
jsonEncode(
WebsocketMessageIdentifyPlayer(
playerID: (playerID),
lobbyID: (lobbyID),
passphrase: (passphrase),
),
),
@ -61,8 +61,9 @@ class ServerConnection {
void disconnectExistingConnection() {
if (channel == null) return;
channel!.sink.close();
channel!.sink.close();
channel = null;
broadcast = const Stream.empty();
}

View File

@ -15,8 +15,12 @@ class ConnectionCubit extends Cubit<ConnectionCubitState> {
return _instance;
}
void connect(String playerID, lobbyID, String? passphrase) {
ServerConnection.getInstance().connect(playerID, lobbyID, passphrase);
void connect(String playerID, String? passphrase) {
ServerConnection.getInstance().connect(playerID, passphrase);
}
void disonnect() {
ServerConnection.getInstance().disconnectExistingConnection();
}
void opponentConnected() {

View File

@ -28,31 +28,9 @@ class _CreateGameWidgetState extends State<CreateGameWidget> {
@override
void initState() {
registerResponse = hostPrivateGame();
registerResponse.then((args) {
if (args == null) return;
chessGameArgs = ChessGameArguments(
lobbyID: args.lobbyID!,
playerID: args.playerID!,
passphrase: args.passphrase);
});
connectToWebsocket(registerResponse);
super.initState();
}
void connectToWebsocket(Future<GameInfo?> resp) {
resp.then((value) {
if (value == null) return;
ConnectionCubit.getInstance().connect(
value.playerID!.uuid,
value.lobbyID!.uuid,
value.passphrase,
);
});
ConnectionCubit().disonnect();
registerResponse = createPrivateGame();
}
@override
@ -82,15 +60,21 @@ class _CreateGameWidgetState extends State<CreateGameWidget> {
child: CircularProgressIndicator(),
);
} else {
String passphrase = snapshot.data?.passphrase ?? "no passphrase";
var passphrase = snapshot.data?.passphrase ?? "no passphrase";
ConnectionCubit().connect(
snapshot.data!.playerID.toString(),
snapshot.data!.passphrase,
);
return BlocListener<ConnectionCubit, ConnectionCubitState>(
listener: (context, state) {
// We wait for our opponent to connect
if (state.opponentConnected) {
//TODO: is goNamed the correct way to navigate?
context.goNamed('game',
pathParameters: {'phrase': passphrase.toURL()},
extra: chessGameArgs);
context.goNamed('game', pathParameters: {
'phrase': passphrase.toURL(),
});
}
},
child: Column(
@ -130,11 +114,11 @@ class _CreateGameWidgetState extends State<CreateGameWidget> {
);
}
Future<GameInfo?> hostPrivateGame() async {
Future<GameInfo?> createPrivateGame() async {
Response response;
try {
response = await http.get(Uri.parse(config.getHostURL()),
response = await http.get(Uri.parse(config.getCreateGameURL()),
headers: {"Accept": "application/json"});
} catch (e) {
log('Exception: ${e.toString()}');

View File

@ -3,7 +3,6 @@ import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:mchess/api/game_info.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
@ -23,65 +22,48 @@ class _JoinGameHandleWidgetState extends State<JoinGameHandleWidget> {
@override
void initState() {
joinGameFuture = joinPrivateGame(widget.passphrase);
joinGameFuture.then(
(value) {
if (value != null) {
switchToGame(value);
}
},
);
super.initState();
ConnectionCubit().disonnect();
joinGameFuture = joinPrivateGame(widget.passphrase);
}
@override
Widget build(BuildContext context) {
return const ChessGame();
}
void switchToGame(GameInfo info) {
var chessGameArgs = ChessGameArguments(
lobbyID: info.lobbyID!,
playerID: info.playerID!,
passphrase: info.passphrase);
ConnectionCubit.getInstance().connect(
info.playerID!.uuid,
info.lobbyID!.uuid,
info.passphrase,
);
if (!chessGameArgs.isValid()) {
context.goNamed('lobbySelector');
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("Game information is corrupted"),
);
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return;
}
return FutureBuilder(
future: joinGameFuture,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
);
} else {
ConnectionCubit.getInstance().connect(
snapshot.data!.playerID!.uuid,
snapshot.data!.passphrase,
);
return const ChessGame();
}
});
}
Future<GameInfo?> joinPrivateGame(String phrase) async {
http.Response response;
var existingInfo = await GameInfo.get();
log('lobbyID: ${existingInfo?.lobbyID} and playerID: ${existingInfo?.playerID} and passphrase: "${existingInfo?.passphrase}"');
var existingInfo = await GameInfo.get(phrase);
log('playerID: ${existingInfo?.playerID} and passphrase: "${existingInfo?.passphrase}"');
GameInfo info;
if (existingInfo?.passphrase == phrase) {
// We have player info for this exact passphrase
info = GameInfo(
playerID: existingInfo?.playerID,
lobbyID: existingInfo?.lobbyID,
passphrase: phrase);
info = GameInfo(playerID: existingInfo?.playerID, passphrase: phrase);
} else {
info = GameInfo(playerID: null, lobbyID: null, passphrase: phrase);
info = GameInfo(playerID: null, passphrase: phrase);
}
try {
response = await http.post(Uri.parse(config.getJoinURL()),
response = await http.post(Uri.parse(config.getJoinGameURL()),
body: jsonEncode(info), headers: {"Accept": "application/json"});
} catch (e) {
log(e.toString());
@ -114,7 +96,6 @@ class _JoinGameHandleWidgetState extends State<JoinGameHandleWidget> {
var info = GameInfo.fromJson(jsonDecode(response.body));
info.store();
log('Player info received from server: ');
log('lobbyID: ${info.lobbyID}');
log('playerID: ${info.playerID}');
log('passphrase: ${info.passphrase}');

View File

@ -20,7 +20,9 @@ class _LobbySelectorState extends State<LobbySelector> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => context.goNamed('createGame'),
onPressed: () {
context.goNamed('createGame');
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
@ -34,7 +36,9 @@ class _LobbySelectorState extends State<LobbySelector> {
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => buildEnterPassphraseDialog(context),
onPressed: () {
buildEnterPassphraseDialog(context);
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@ -2,7 +2,6 @@ import 'dart:developer';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:mchess/connection/ws_connection.dart';
import 'package:mchess/pages/join_game_handle_widget.dart';
import 'package:mchess/pages/lobby_selector.dart';
import 'package:mchess/pages/create_game_widget.dart';
@ -40,8 +39,6 @@ class ChessAppRouter {
path: 'game/:phrase',
name: 'game',
builder: (context, state) {
ServerConnection.getInstance().disconnectExistingConnection();
var urlPhrase = state.pathParameters['phrase'];
if (urlPhrase == null) {
log('in /game route builder: url phrase null');
@ -49,8 +46,7 @@ class ChessAppRouter {
}
return JoinGameHandleWidget(
passphrase: urlPhrase.toPhraseWithSpaces(),
);
passphrase: urlPhrase.toPhraseWithSpaces());
},
)
],

View File

@ -1,9 +1,9 @@
const prodURL = 'chess.sw-gross.de:9999';
const debugURL = 'localhost:8080';
const useDbgUrl = false;
const useDbgUrl = true;
String getHostURL() {
String getCreateGameURL() {
var prot = 'https';
var domain = prodURL;
if (useDbgUrl) {
@ -13,7 +13,7 @@ String getHostURL() {
return '$prot://$domain/api/hostPrivate';
}
String getJoinURL() {
String getJoinGameURL() {
var prot = 'https';
var domain = prodURL;
if (useDbgUrl) {