fuck shit up

This commit is contained in:
Marco 2024-05-19 17:22:06 +02:00
parent c802251c9d
commit ffbb1dd95a
10 changed files with 59 additions and 269 deletions

View File

@ -1,97 +1,23 @@
import 'package:shared_preferences/shared_preferences.dart'; class CreateGameResponse {
import 'package:uuid/uuid.dart';
class PlayerInfo {
final UuidValue? playerID;
final UuidValue? lobbyID;
final String? passphrase; final String? passphrase;
const PlayerInfo({ const CreateGameResponse({
required this.playerID,
required this.lobbyID,
required this.passphrase, required this.passphrase,
}); });
factory PlayerInfo.empty() { factory CreateGameResponse.empty() {
return const PlayerInfo(playerID: null, lobbyID: null, passphrase: null); return const CreateGameResponse(passphrase: null);
} }
factory PlayerInfo.fromJson(Map<String, dynamic> json) { factory CreateGameResponse.fromJson(Map<String, dynamic> json) {
final playerid = UuidValue.fromString(json['playerID']);
final lobbyid = UuidValue.fromString(json['lobbyID']);
final passphrase = json['passphrase']; final passphrase = json['passphrase'];
return PlayerInfo( return CreateGameResponse(passphrase: passphrase);
playerID: playerid, lobbyID: lobbyid, passphrase: passphrase);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
String? pid;
String? lid;
if (playerID != null) {
pid = playerID.toString();
}
if (lobbyID != null) {
lid = lobbyID.toString();
}
return { return {
'playerID': pid,
'lobbyID': lid,
'passphrase': passphrase, '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<PlayerInfo?> 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<String, dynamic> toJson() =>
{'lobbyID': lobbyID, 'playerID': playerID, 'passphrase': passphrase};
} }

View File

@ -40,19 +40,11 @@ class ServerConnection {
channel!.sink.add(message); channel!.sink.add(message);
} }
void connect(String playerID, lobbyID, String? passphrase) { void connect(String? passphrase) {
disconnectExistingConnection(); disconnectExistingConnection();
channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL())); channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL()));
send( send(jsonEncode(passphrase));
jsonEncode(
WebsocketMessageIdentifyPlayer(
playerID: (playerID),
lobbyID: (lobbyID),
passphrase: (passphrase),
),
),
);
log(channel!.closeCode.toString()); log(channel!.closeCode.toString());
broadcast = channel!.stream.asBroadcastStream(); broadcast = channel!.stream.asBroadcastStream();

View File

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

View File

@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mchess/chess/chess_app.dart'; import 'package:mchess/chess/chess_app.dart';
void main() { void main() {
GoRouter.optionURLReflectsImperativeAPIs = true;
runApp(const ChessApp()); runApp(const ChessApp());
} }

View File

@ -9,17 +9,10 @@ import 'package:mchess/chess_bloc/tap_bloc.dart';
import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
import 'package:mchess/utils/widgets/promotion_dialog.dart'; import 'package:mchess/utils/widgets/promotion_dialog.dart';
import 'package:universal_platform/universal_platform.dart'; import 'package:universal_platform/universal_platform.dart';
import 'package:uuid/uuid.dart';
class ChessGame extends StatefulWidget { class ChessGame extends StatefulWidget {
final UuidValue playerID;
final UuidValue lobbyID;
final String? passphrase; final String? passphrase;
const ChessGame( const ChessGame({required this.passphrase, super.key});
{required this.playerID,
required this.lobbyID,
required this.passphrase,
super.key});
@override @override
State<ChessGame> createState() => _ChessGameState(); State<ChessGame> createState() => _ChessGameState();
@ -85,29 +78,3 @@ class _ChessGameState extends State<ChessGame> {
); );
} }
} }
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;
}
}

View File

@ -1,4 +1,5 @@
import 'dart:developer'; import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -13,36 +14,26 @@ import 'dart:convert';
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;
class HostGameWidget extends StatefulWidget { class CreateGameWidget extends StatefulWidget {
const HostGameWidget({super.key}); const CreateGameWidget({super.key});
@override @override
State<HostGameWidget> createState() => _HostGameWidgetState(); State<CreateGameWidget> createState() => _CreateGameWidgetState();
} }
class _HostGameWidgetState extends State<HostGameWidget> { class _CreateGameWidgetState extends State<CreateGameWidget> {
late Future<PlayerInfo?> registerResponse;
late ChessGameArguments chessGameArgs;
@override @override
void initState() { void initState() {
registerResponse = hostPrivateGame(); registerResponse = createPrivateGame();
connectToWebsocket(registerResponse); connectToWebsocket(registerResponse);
super.initState(); super.initState();
} }
void connectToWebsocket(Future<PlayerInfo?> resp) { void connectToWebsocket(Future<CreateGameResponse?> resp) {
resp.then((value) { resp.then((value) {
if (value == null) return; if (value == null) return;
chessGameArgs = ChessGameArguments(
lobbyID: value.lobbyID!,
playerID: value.playerID!,
passphrase: value.passphrase);
ConnectionCubit.getInstance().connect( ConnectionCubit.getInstance().connect(
value.playerID!.uuid,
value.lobbyID!.uuid,
value.passphrase, value.passphrase,
); );
}); });
@ -67,8 +58,8 @@ 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/$passphrase',
context.pushReplacement('/game', extra: chessGameArgs); extra: chessGameArgs);
} }
}, },
child: Column( child: Column(
@ -108,11 +99,11 @@ class _HostGameWidgetState extends State<HostGameWidget> {
); );
} }
Future<PlayerInfo?> hostPrivateGame() async { Future<CreateGameResponse?> createPrivateGame() async {
Response response; Response response;
try { try {
response = await http.get(Uri.parse(config.getHostURL()), response = await http.get(Uri.parse(config.getCreateGameURL()),
headers: {"Accept": "application/json"}); headers: {"Accept": "application/json"});
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());
@ -131,10 +122,9 @@ class _HostGameWidgetState extends State<HostGameWidget> {
return null; return null;
} }
if (response.statusCode == 200) { if (response.statusCode == HttpStatus.ok) {
log(response.body); log(response.body);
var info = PlayerInfo.fromJson(jsonDecode(response.body)); return CreateGameResponse.fromJson(jsonDecode(response.body));
return info;
} }
return null; return null;
} }

View File

@ -1,15 +1,8 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart'; 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:mchess/api/register.dart';
import 'package:mchess/connection/ws_connection.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/utils/passpharse.dart';
import 'package:mchess/utils/config.dart' as config;
class LobbySelector extends StatefulWidget { class LobbySelector extends StatefulWidget {
const LobbySelector({super.key}); const LobbySelector({super.key});
@ -21,7 +14,7 @@ class LobbySelector extends StatefulWidget {
class _LobbySelectorState extends State<LobbySelector> { class _LobbySelectorState extends State<LobbySelector> {
final buttonStyle = const ButtonStyle(); final buttonStyle = const ButtonStyle();
final phraseController = TextEditingController(); final phraseController = TextEditingController();
late Future<PlayerInfo?> joinGameFuture; late Future<String?> joinGameFuture;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -31,7 +24,7 @@ class _LobbySelectorState extends State<LobbySelector> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: () => buildJoinOrHostDialog(context), onPressed: () => buildJoinOrCreateDialog(context),
child: const Row( child: const Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -49,7 +42,7 @@ class _LobbySelectorState extends State<LobbySelector> {
); );
} }
Future<void> buildJoinOrHostDialog(BuildContext context) { Future<void> buildJoinOrCreateDialog(BuildContext context) {
//TODO: find a better place to disconnect old websocket connection //TODO: find a better place to disconnect old websocket connection
ServerConnection.getInstance().disconnectExistingConnection(); ServerConnection.getInstance().disconnectExistingConnection();
@ -59,17 +52,17 @@ class _LobbySelectorState extends State<LobbySelector> {
return Scaffold( return Scaffold(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
body: AlertDialog( body: AlertDialog(
title: const Text('Host or join?'), title: const Text('Create or join?'),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
child: const Text('Cancel'), child: const Text('Cancel'),
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
TextButton( TextButton(
child: const Text('Host'), child: const Text('Create'),
onPressed: () { onPressed: () {
context.pop(); //close dialog before going to host context.pop(); //close dialog before going to createGame
context.goNamed('host'); context.goNamed('createGame');
}), }),
TextButton( TextButton(
child: const Text('Join'), child: const Text('Join'),
@ -126,106 +119,15 @@ class _LobbySelectorState extends State<LobbySelector> {
} }
void submitPassphrase(BuildContext ctx) { void submitPassphrase(BuildContext ctx) {
joinGameFuture = joinPrivateGame(ctx); switchToGame(phraseController.text);
joinGameFuture.then((value) {
if (value != null) {
phraseController.clear();
ctx.pop();
switchToGame(value);
}
});
} }
void switchToGame(PlayerInfo info) { void switchToGame(String phrase) {
var chessGameArgs = ChessGameArguments(
lobbyID: info.lobbyID!,
playerID: info.playerID!,
passphrase: info.passphrase);
ConnectionCubit.getInstance().connect( ConnectionCubit.getInstance().connect(
info.playerID!.uuid, phrase,
info.lobbyID!.uuid,
info.passphrase,
); );
if (!chessGameArgs.isValid()) { var urlPassphrase = phrase.phraseToURL();
context.goNamed('lobbySelector'); context.goNamed('game', pathParameters: {'phrase': urlPassphrase});
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<PlayerInfo?> 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;
} }
} }

View File

@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:mchess/pages/chess_game.dart'; import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/pages/lobby_selector.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<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
@ -27,13 +27,13 @@ class ChessAppRouter {
}, },
routes: [ routes: [
GoRoute( GoRoute(
path: 'host', path: 'createGame',
name: 'host', name: 'createGame',
builder: (context, state) { builder: (context, state) {
return const HostGameWidget(); return const CreateGameWidget();
}), }),
GoRoute( GoRoute(
path: 'game', path: 'game/:phrase',
name: 'game', name: 'game',
builder: (context, state) { builder: (context, state) {
var args = state.extra as ChessGameArguments; var args = state.extra as ChessGameArguments;

View File

@ -1,9 +1,9 @@
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 getCreateGameURL() {
var prot = 'https'; var prot = 'https';
var domain = prodURL; var domain = prodURL;
if (useDbgUrl) { if (useDbgUrl) {

15
lib/utils/passpharse.dart Normal file
View File

@ -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()}";
}
}