diff --git a/lib/api/register.dart b/lib/api/register.dart index 927b7c2..6717092 100644 --- a/lib/api/register.dart +++ b/lib/api/register.dart @@ -1,97 +1,23 @@ -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:uuid/uuid.dart'; - -class PlayerInfo { - final UuidValue? playerID; - final UuidValue? lobbyID; +class CreateGameResponse { final String? passphrase; - const PlayerInfo({ - required this.playerID, - required this.lobbyID, + const CreateGameResponse({ required this.passphrase, }); - factory PlayerInfo.empty() { - return const PlayerInfo(playerID: null, lobbyID: null, passphrase: null); + factory CreateGameResponse.empty() { + return const CreateGameResponse(passphrase: null); } - factory PlayerInfo.fromJson(Map json) { - final playerid = UuidValue.fromString(json['playerID']); - final lobbyid = UuidValue.fromString(json['lobbyID']); + factory CreateGameResponse.fromJson(Map json) { final passphrase = json['passphrase']; - return PlayerInfo( - playerID: playerid, lobbyID: lobbyid, passphrase: passphrase); + return CreateGameResponse(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(); - - await prefs.setBool("contains", true); - await prefs.setString("playerID", playerID.toString()); - await prefs.setString("lobbyID", lobbyID.toString()); - await prefs.setString("passphrase", passphrase.toString()); - } - - void delete() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - - await prefs.setBool("contains", false); - } - - static Future 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; - } - - return PlayerInfo( - playerID: UuidValue.fromString(playerID), - lobbyID: UuidValue.fromString(lobbyID), - passphrase: passphrase); - } -} - -class WebsocketMessageIdentifyPlayer { - final String playerID; - final String lobbyID; - final String? passphrase; - - const WebsocketMessageIdentifyPlayer({ - required this.playerID, - required this.lobbyID, - required this.passphrase, - }); - - Map toJson() => - {'lobbyID': lobbyID, 'playerID': playerID, 'passphrase': passphrase}; } diff --git a/lib/connection/ws_connection.dart b/lib/connection/ws_connection.dart index 3c43b57..c4f0778 100644 --- a/lib/connection/ws_connection.dart +++ b/lib/connection/ws_connection.dart @@ -40,19 +40,11 @@ class ServerConnection { channel!.sink.add(message); } - void connect(String playerID, lobbyID, String? passphrase) { + void connect(String? passphrase) { disconnectExistingConnection(); channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL())); - send( - jsonEncode( - WebsocketMessageIdentifyPlayer( - playerID: (playerID), - lobbyID: (lobbyID), - passphrase: (passphrase), - ), - ), - ); + send(jsonEncode(passphrase)); log(channel!.closeCode.toString()); broadcast = channel!.stream.asBroadcastStream(); diff --git a/lib/connection_cubit/connection_cubit.dart b/lib/connection_cubit/connection_cubit.dart index 9c9c3e2..6213af2 100644 --- a/lib/connection_cubit/connection_cubit.dart +++ b/lib/connection_cubit/connection_cubit.dart @@ -15,8 +15,8 @@ class ConnectionCubit extends Cubit { return _instance; } - void connect(String playerID, lobbyID, String? passphrase) { - ServerConnection.getInstance().connect(playerID, lobbyID, passphrase); + void connect(String? passphrase) { + ServerConnection.getInstance().connect(passphrase); } void opponentConnected() { diff --git a/lib/main.dart b/lib/main.dart index 5a914d7..34e0a50 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:mchess/chess/chess_app.dart'; void main() { - GoRouter.optionURLReflectsImperativeAPIs = true; runApp(const ChessApp()); } diff --git a/lib/pages/chess_game.dart b/lib/pages/chess_game.dart index 13f33ce..6d46e7e 100644 --- a/lib/pages/chess_game.dart +++ b/lib/pages/chess_game.dart @@ -9,17 +9,10 @@ import 'package:mchess/chess_bloc/tap_bloc.dart'; import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/widgets/promotion_dialog.dart'; import 'package:universal_platform/universal_platform.dart'; -import 'package:uuid/uuid.dart'; class ChessGame extends StatefulWidget { - final UuidValue playerID; - final UuidValue lobbyID; final String? passphrase; - const ChessGame( - {required this.playerID, - required this.lobbyID, - required this.passphrase, - super.key}); + const ChessGame({required this.passphrase, super.key}); @override State createState() => _ChessGameState(); @@ -85,29 +78,3 @@ class _ChessGameState extends State { ); } } - -class ChessGameArguments { - final UuidValue lobbyID; - final UuidValue playerID; - final String? passphrase; - - ChessGameArguments({ - required this.lobbyID, - required this.playerID, - required this.passphrase, - }); - - bool isValid() { - try { - lobbyID.validate(); - playerID.validate(); - } catch (e) { - return false; - } - - if (passphrase == null) return false; - if (passphrase!.isEmpty) return false; - - return true; - } -} diff --git a/lib/pages/host_game.dart b/lib/pages/create_game.dart similarity index 78% rename from lib/pages/host_game.dart rename to lib/pages/create_game.dart index 757b171..215f866 100644 --- a/lib/pages/host_game.dart +++ b/lib/pages/create_game.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -13,36 +14,26 @@ import 'dart:convert'; import 'package:mchess/pages/chess_game.dart'; import 'package:mchess/utils/config.dart' as config; -class HostGameWidget extends StatefulWidget { - const HostGameWidget({super.key}); +class CreateGameWidget extends StatefulWidget { + const CreateGameWidget({super.key}); @override - State createState() => _HostGameWidgetState(); + State createState() => _CreateGameWidgetState(); } -class _HostGameWidgetState extends State { - late Future registerResponse; - late ChessGameArguments chessGameArgs; - +class _CreateGameWidgetState extends State { @override void initState() { - registerResponse = hostPrivateGame(); + registerResponse = createPrivateGame(); connectToWebsocket(registerResponse); super.initState(); } - void connectToWebsocket(Future resp) { + void connectToWebsocket(Future resp) { resp.then((value) { if (value == null) return; - chessGameArgs = ChessGameArguments( - lobbyID: value.lobbyID!, - playerID: value.playerID!, - passphrase: value.passphrase); - ConnectionCubit.getInstance().connect( - value.playerID!.uuid, - value.lobbyID!.uuid, value.passphrase, ); }); @@ -67,8 +58,8 @@ 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); + context.pushReplacement('/game/$passphrase', + extra: chessGameArgs); } }, child: Column( @@ -108,11 +99,11 @@ class _HostGameWidgetState extends State { ); } - Future hostPrivateGame() async { + Future 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(e.toString()); @@ -131,10 +122,9 @@ class _HostGameWidgetState extends State { return null; } - if (response.statusCode == 200) { + if (response.statusCode == HttpStatus.ok) { log(response.body); - var info = PlayerInfo.fromJson(jsonDecode(response.body)); - return info; + return CreateGameResponse.fromJson(jsonDecode(response.body)); } return null; } diff --git a/lib/pages/lobby_selector.dart b/lib/pages/lobby_selector.dart index 5c3385b..5ce188a 100644 --- a/lib/pages/lobby_selector.dart +++ b/lib/pages/lobby_selector.dart @@ -1,15 +1,8 @@ -import 'dart:convert'; -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/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:mchess/utils/passpharse.dart'; class LobbySelector extends StatefulWidget { const LobbySelector({super.key}); @@ -21,7 +14,7 @@ class LobbySelector extends StatefulWidget { class _LobbySelectorState extends State { final buttonStyle = const ButtonStyle(); final phraseController = TextEditingController(); - late Future joinGameFuture; + late Future joinGameFuture; @override Widget build(BuildContext context) { @@ -31,7 +24,7 @@ class _LobbySelectorState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( - onPressed: () => buildJoinOrHostDialog(context), + onPressed: () => buildJoinOrCreateDialog(context), child: const Row( mainAxisSize: MainAxisSize.min, children: [ @@ -49,7 +42,7 @@ class _LobbySelectorState extends State { ); } - Future buildJoinOrHostDialog(BuildContext context) { + Future buildJoinOrCreateDialog(BuildContext context) { //TODO: find a better place to disconnect old websocket connection ServerConnection.getInstance().disconnectExistingConnection(); @@ -59,17 +52,17 @@ class _LobbySelectorState extends State { return Scaffold( backgroundColor: Colors.transparent, body: AlertDialog( - title: const Text('Host or join?'), + title: const Text('Create or join?'), actions: [ TextButton( child: const Text('Cancel'), onPressed: () => context.pop(), ), TextButton( - child: const Text('Host'), + child: const Text('Create'), onPressed: () { - context.pop(); //close dialog before going to host - context.goNamed('host'); + context.pop(); //close dialog before going to createGame + context.goNamed('createGame'); }), TextButton( child: const Text('Join'), @@ -126,106 +119,15 @@ class _LobbySelectorState extends State { } void submitPassphrase(BuildContext ctx) { - joinGameFuture = joinPrivateGame(ctx); - joinGameFuture.then((value) { - if (value != null) { - phraseController.clear(); - ctx.pop(); - switchToGame(value); - } - }); + switchToGame(phraseController.text); } - void switchToGame(PlayerInfo info) { - var chessGameArgs = ChessGameArguments( - lobbyID: info.lobbyID!, - playerID: info.playerID!, - passphrase: info.passphrase); - + void switchToGame(String phrase) { ConnectionCubit.getInstance().connect( - info.playerID!.uuid, - info.lobbyID!.uuid, - info.passphrase, + phrase, ); - 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; - } - - context.goNamed('game', extra: chessGameArgs); - } - - Future joinPrivateGame(BuildContext context) async { - http.Response response; - - 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: decodedInfo, headers: {"Accept": "application/json"}); - } catch (e) { - log(e.toString()); - - if (!context.mounted) return null; - - const snackBar = SnackBar( - backgroundColor: Colors.amberAccent, - content: Text("mChess server is not responding. Try again or give up"), - ); - ScaffoldMessenger.of(context).clearSnackBars(); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - return null; - } - - if (response.statusCode == HttpStatus.notFound) { - const snackBar = SnackBar( - backgroundColor: Colors.amberAccent, - content: Text("Passphrase could not be found."), - ); - - if (!context.mounted) return null; - - ScaffoldMessenger.of(context).clearSnackBars(); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - return null; - } - - 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}'); - log('passphrase: ${info.passphrase}'); - - return info; - } - - return null; + var urlPassphrase = phrase.phraseToURL(); + context.goNamed('game', pathParameters: {'phrase': urlPassphrase}); } } diff --git a/lib/utils/chess_router.dart b/lib/utils/chess_router.dart index b9f667a..1d5ffda 100644 --- a/lib/utils/chess_router.dart +++ b/lib/utils/chess_router.dart @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; import 'package:mchess/pages/chess_game.dart'; import 'package:mchess/pages/lobby_selector.dart'; -import 'package:mchess/pages/host_game.dart'; +import 'package:mchess/pages/create_game.dart'; final navigatorKey = GlobalKey(); @@ -27,13 +27,13 @@ class ChessAppRouter { }, routes: [ GoRoute( - path: 'host', - name: 'host', + path: 'createGame', + name: 'createGame', builder: (context, state) { - return const HostGameWidget(); + return const CreateGameWidget(); }), GoRoute( - path: 'game', + path: 'game/:phrase', name: 'game', builder: (context, state) { var args = state.extra as ChessGameArguments; diff --git a/lib/utils/config.dart b/lib/utils/config.dart index 863dedb..4b2f7db 100644 --- a/lib/utils/config.dart +++ b/lib/utils/config.dart @@ -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) { diff --git a/lib/utils/passpharse.dart b/lib/utils/passpharse.dart new file mode 100644 index 0000000..c8bf9dd --- /dev/null +++ b/lib/utils/passpharse.dart @@ -0,0 +1,15 @@ +extension PassphraseURL on String { + String phraseToURL() { + var words = split(' '); + + for (var i = 0; i < words.length; i++) { + words[i] = words[i].capitalize(); + } + + return words.join(); + } + + String capitalize() { + return "${this[0].toUpperCase()}${substring(1).toLowerCase()}"; + } +}