A lot of changes again?!?

This commit is contained in:
Marco 2023-06-30 01:49:18 +02:00
parent 43fca47dae
commit 52540ec96c
14 changed files with 543 additions and 178 deletions

View File

@ -1,30 +1,43 @@
import 'package:uuid/uuid.dart';
class ResponseFromRegisteringGame {
final UuidValue playerID;
final UuidValue lobbyID;
class PlayerInfo {
final UuidValue? playerID;
final UuidValue? lobbyID;
final String? passphrase;
const ResponseFromRegisteringGame({
const PlayerInfo({
required this.playerID,
required this.lobbyID,
required this.passphrase,
});
factory ResponseFromRegisteringGame.fromJson(Map<String, dynamic> json) {
factory PlayerInfo.fromJson(Map<String, dynamic> json) {
final playerid = UuidValue(json['playerID']);
final lobbyid = UuidValue(json['lobbyID']);
final passphrase = json['passphrase'];
return ResponseFromRegisteringGame(playerID: playerid, lobbyID: lobbyid);
return PlayerInfo(
playerID: playerid, lobbyID: lobbyid, passphrase: passphrase);
}
Map<String, dynamic> toJson() => {
'playerID': playerID,
'lobbyID': 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};
Map<String, dynamic> toJson() =>
{'lobbyID': lobbyID, 'playerID': playerID, 'passphrase': passphrase};
}

View File

@ -77,7 +77,7 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
ServerConnection.getInstance().send(jsonEncode(apiMessage));
//Temporary chess position until server responds with acknoledgement
//Temporary chess position until server responds with acknowledgement
var move = ChessMove.fromApiMove(apiMove);
var tempPosition = ChessPosition.getInstance().copyOfCurrentPosition;
tempPosition[move.to] = tempPosition[move.from] ?? const ChessPiece.none();

View File

@ -104,7 +104,7 @@ class ChessPosition {
logString = '$logString\n';
}
print(logString);
log(logString);
}
void logHistory(ChessMoveHistory hist) {

View File

@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart';
import 'package:mchess/api/websocket_message.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/chess_bloc/chess_position.dart';
import 'package:mchess/api/register.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/utils/chess_utils.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
@ -34,9 +34,7 @@ class ServerConnection {
counter++;
}
void connect(String playerID, lobbyID) {
if (wasConnected) channel.sink.close();
void connect(String playerID, lobbyID, String? passphrase) {
if (kDebugMode) {
channel =
WebSocketChannel.connect(Uri.parse('ws://localhost:8080/api/ws'));
@ -44,15 +42,18 @@ class ServerConnection {
channel = WebSocketChannel.connect(
Uri.parse('wss://chess.sw-gross.de:9999/api/ws'));
}
send(jsonEncode(WebsocketMessageIdentifyPlayer(
playerID: (playerID), lobbyID: (lobbyID))));
send(
jsonEncode(
WebsocketMessageIdentifyPlayer(
playerID: (playerID),
lobbyID: (lobbyID),
passphrase: (passphrase),
),
),
);
log(channel.closeCode.toString());
wasConnected = true;
broadcast = channel.stream.asBroadcastStream();
broadcast.listen(handleIncomingData);
}
@ -78,6 +79,7 @@ class ServerConnection {
void handleIncomingColorDeterminedMessage(ApiWebsocketMessage apiMessage) {
ChessBloc.getInstance().add(
ColorDetermined(myColor: ChessColor.fromApiColor(apiMessage.color!)));
ConnectionCubit.getInstance().opponentConnected();
}
void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) {

View File

@ -15,16 +15,19 @@ class ConnectionCubit extends Cubit<ConnectionCubitState> {
return _instance;
}
void connect(String playerID, lobbyID) {
ServerConnection.getInstance().connect(playerID, lobbyID);
void connect(String playerID, lobbyID, String? passphrase) {
ServerConnection.getInstance().connect(playerID, lobbyID, passphrase);
}
void opponentConnected() {
emit(ConnectionCubitState(true));
}
}
class ConnectionCubitState {
final bool reconnecting;
final bool opponentConnected;
ConnectionCubitState(this.reconnecting);
ConnectionCubitState(this.opponentConnected);
factory ConnectionCubitState.init() {
return ConnectionCubitState(false);

View File

@ -6,16 +6,18 @@ import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess/chess_board.dart';
import 'package:mchess/chess/turn_indicator_widget.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/connection/ws_connection.dart';
import 'package:mchess/utils/widgets/server_log_widget.dart';
import 'package:uuid/uuid.dart';
class ChessGame extends StatefulWidget {
final UuidValue playerID;
final UuidValue lobbyID;
const ChessGame({required this.playerID, required this.lobbyID, super.key});
final String? passphrase;
const ChessGame(
{required this.playerID,
required this.lobbyID,
required this.passphrase,
super.key});
@override
State<ChessGame> createState() => _ChessGameState();
@ -25,49 +27,48 @@ class _ChessGameState extends State<ChessGame> {
@override
void initState() {
super.initState();
ConnectionCubit.getInstance()
.connect(widget.playerID.uuid, widget.lobbyID.uuid);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FittedBox(
fit: BoxFit.contain,
child: Column(
children: [
if (kDebugMode)
StreamBuilder(
stream: ServerConnection.getInstance().broadcast,
builder: (context, snapshot) {
return ServerLogWidget(
snapshot.data ?? "<snapshot empty>",
textColor: Colors.white,
);
},
),
Container(
margin: const EdgeInsets.all(20),
child: BlocBuilder<ChessBloc, ChessBoardState>(
builder: (context, state) {
return ChessBoard(
bState: state,
);
},
),
body: FittedBox(
fit: BoxFit.contain,
child: Column(
children: [
if (kDebugMode) const ServerLogWidget(textColor: Colors.white),
Container(
margin: const EdgeInsets.all(20),
child: BlocBuilder<ChessBloc, ChessBoardState>(
builder: (context, state) {
return ChessBoard(
bState: state,
);
},
),
if (kDebugMode) const TurnIndicator(),
],
),
),
if (kDebugMode) const TurnIndicator(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.goNamed('lobbySelector');
context.push('/');
},
child: const Icon(Icons.arrow_back),
child: const Icon(Icons.cancel),
),
);
}
}
class ChessGameArguments {
final UuidValue lobbyID;
final UuidValue playerID;
final String? passphrase;
ChessGameArguments({
required this.lobbyID,
required this.playerID,
required this.passphrase,
});
}

144
lib/pages/host_game.dart Normal file
View File

@ -0,0 +1,144 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart';
import 'package:mchess/api/register.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:mchess/pages/chess_game.dart';
class HostGameWidget extends StatefulWidget {
const HostGameWidget({super.key});
@override
State<HostGameWidget> createState() => _HostGameWidgetState();
}
class _HostGameWidgetState extends State<HostGameWidget> {
late Future<PlayerInfo?> registerResponse;
late ChessGameArguments chessGameArgs;
@override
void initState() {
ChessBloc.getInstance().add(InitBoard());
registerResponse = hostPrivateGame();
connectToWebsocket(registerResponse);
super.initState();
}
void connectToWebsocket(Future<PlayerInfo?> 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,
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<PlayerInfo?>(
future: registerResponse,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
);
} else {
String passphrase = snapshot.data?.passphrase ?? "no passphrase";
return BlocListener<ConnectionCubit, ConnectionCubitState>(
listener: (context, state) {
// We wait for our opponent to connect
if (state.opponentConnected) {
context.push('/game', extra: chessGameArgs);
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Give this phrase to your friend and sit tight:',
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
const SizedBox(height: 25),
Text(
passphrase,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 25),
const CircularProgressIndicator()
],
),
);
}
},
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.cancel),
onPressed: () {
context.push('/');
},
),
);
}
Future<PlayerInfo?> hostPrivateGame() async {
String addr;
Response response;
if (kDebugMode) {
addr = 'http://localhost:8080/api/hostPrivate';
} else {
addr = 'https://chess.sw-gross.de:9999/api/hostPrivate';
}
try {
response = await http
.get(Uri.parse(addr), 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"),
);
Future.delayed(const Duration(seconds: 2), () {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.pop();
});
return null;
}
if (response.statusCode == 200) {
log(response.body);
return PlayerInfo.fromJson(jsonDecode(response.body));
}
return null;
}
}

118
lib/pages/join_game.dart Normal file
View File

@ -0,0 +1,118 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:mchess/api/register.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/pages/chess_game.dart';
class JoinGameWidget extends StatefulWidget {
const JoinGameWidget({
super.key,
});
@override
State<JoinGameWidget> createState() => _JoinGameWidgetState();
}
class _JoinGameWidgetState extends State<JoinGameWidget> {
final myController = TextEditingController();
late Future<PlayerInfo?> joinGameFuture;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: TextField(
controller: myController,
decoration: InputDecoration(
hintText: 'Enter passphrase here',
suffixIcon: IconButton(
onPressed: () {
joinGameFuture = joinPrivateGame();
switchToGame(joinGameFuture);
},
icon: const Icon(Icons.check),
)),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.push('/');
},
child: const Icon(Icons.cancel),
),
);
}
void switchToGame(Future<PlayerInfo?> resp) {
resp.then((value) {
if (value == null) return;
var chessGameArgs = ChessGameArguments(
lobbyID: value.lobbyID!,
playerID: value.playerID!,
passphrase: value.passphrase);
ConnectionCubit.getInstance().connect(
value.playerID!.uuid,
value.lobbyID!.uuid,
value.passphrase,
);
context.push('/game', extra: chessGameArgs);
});
}
Future<PlayerInfo?> joinPrivateGame() async {
String addr;
Response response;
// server expects us to send the passphrase
var info = PlayerInfo(
playerID: null, lobbyID: null, passphrase: myController.text);
if (kDebugMode) {
addr = 'http://localhost:8080/api/joinPrivate';
} else {
addr = 'https://chess.sw-gross.de:9999/api/joinPrivate';
}
try {
response = await http.post(Uri.parse(addr),
body: jsonEncode(info), 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"),
);
Future.delayed(const Duration(seconds: 2), () {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.pop();
});
return null;
}
if (response.statusCode == 200) {
var info = PlayerInfo.fromJson(jsonDecode(response.body));
log('Player info received from server: ');
log('lobbyID: ${info.lobbyID}');
log('playerID: ${info.playerID}');
log('passphrase: ${info.passphrase}');
return info;
}
return null;
}
}

View File

@ -4,17 +4,76 @@ import 'package:go_router/go_router.dart';
class LobbySelector extends StatelessWidget {
const LobbySelector({super.key});
final buttonStyle = const ButtonStyle();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton(
onPressed: () {
context.goNamed('prepareChessGame');
},
child: const Text('Random lobby'),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
context.push('/prepareRandom');
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.question_mark),
SizedBox(
width: 10,
),
Text('Random')
],
),
),
const SizedBox(
height: 25,
),
ElevatedButton(
onPressed: () {
_dialogBuilder(context);
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.lock),
SizedBox(
width: 10,
),
Text('Private')
],
),
),
],
),
),
);
}
Future<void> _dialogBuilder(BuildContext context) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Host or join?'),
actions: <Widget>[
TextButton(
child: const Text('Host'),
onPressed: () {
context.push('/host');
},
),
TextButton(
child: const Text('Join'),
onPressed: () {
context.push('/join');
},
),
],
);
},
);
}
}

View File

@ -1,96 +0,0 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart';
import 'package:mchess/api/register.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/pages/chess_game.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class PrepareChessGameWidget extends StatefulWidget {
const PrepareChessGameWidget({super.key});
@override
State<PrepareChessGameWidget> createState() => _PrepareChessGameWidgetState();
}
class _PrepareChessGameWidgetState extends State<PrepareChessGameWidget> {
late Future randomGameResponse;
@override
void initState() {
ChessBloc.getInstance().add(InitBoard());
randomGameResponse = registerForRandomGame();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: randomGameResponse,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
log('Response from registering to random game ${snapshot.data}');
if (snapshot.data != null) {
return ChessGame(
playerID: snapshot.data!.playerID,
lobbyID: snapshot.data!.lobbyID,
);
}
}
return const Center(
child: SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
),
);
},
),
);
}
Future<ResponseFromRegisteringGame?> registerForRandomGame() async {
String addr;
if (kDebugMode) {
addr = 'http://localhost:8080/api/random';
} else {
addr = 'https://chess.sw-gross.de:9999/api/random';
}
Response response;
try {
response = await http
.get(Uri.parse(addr), headers: {"Accept": "application/json"});
} catch (e) {
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content:
Text("mChess server is not responding. Try again or give up"));
if (!context.mounted) return null;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Future.delayed(const Duration(seconds: 2), () {
context.goNamed('lobbySelector');
});
return null;
}
if (response.statusCode == 200) {
log(response.body);
return ResponseFromRegisteringGame.fromJson(jsonDecode(response.body));
}
return null;
}
}

View File

@ -0,0 +1,97 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart';
import 'package:mchess/api/register.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/pages/chess_game.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class PrepareRandomGameWidget extends StatefulWidget {
const PrepareRandomGameWidget({super.key});
@override
State<PrepareRandomGameWidget> createState() =>
_PrepareRandomGameWidgetState();
}
class _PrepareRandomGameWidgetState extends State<PrepareRandomGameWidget> {
late Future randomGameResponse;
@override
void initState() {
ChessBloc.getInstance().add(InitBoard());
randomGameResponse = registerForRandomGame();
goToGameWhenResponseIsHere(randomGameResponse as Future<PlayerInfo?>);
super.initState();
}
void goToGameWhenResponseIsHere(Future<PlayerInfo?> resp) {
resp.then((value) {
if (value == null) return;
context.push(
'/game',
extra: ChessGameArguments(
lobbyID: value.lobbyID!,
playerID: value.playerID!,
passphrase: value.passphrase),
);
});
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
),
),
);
}
Future<PlayerInfo?> registerForRandomGame() async {
String addr;
Response response;
if (kDebugMode) {
addr = 'http://localhost:8080/api/random';
} else {
addr = 'https://chess.sw-gross.de:9999/api/random';
}
try {
response = await http
.get(Uri.parse(addr), headers: {"Accept": "application/json"});
} catch (e) {
if (!context.mounted) return null;
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("mChess server is not responding. Try again or give up"),
);
Future.delayed(const Duration(seconds: 2), () {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.pop();
});
return null;
}
if (response.statusCode == 200) {
log(response.body);
return PlayerInfo.fromJson(jsonDecode(response.body));
}
return null;
}
}

View File

@ -1,6 +1,9 @@
import 'package:go_router/go_router.dart';
import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/pages/join_game.dart';
import 'package:mchess/pages/lobby_selector.dart';
import 'package:mchess/pages/prepare_chess_game.dart';
import 'package:mchess/pages/host_game.dart';
import 'package:mchess/pages/prepare_random_game.dart';
class ChessAppRouter {
static final ChessAppRouter _instance = ChessAppRouter._internal();
@ -19,11 +22,36 @@ class ChessAppRouter {
builder: (context, state) => const LobbySelector(),
),
GoRoute(
path: '/play',
name: 'prepareChessGame',
path: '/prepareRandom',
name: 'prepareRandom',
builder: (context, state) {
return const PrepareChessGameWidget();
})
return const PrepareRandomGameWidget();
}),
GoRoute(
path: '/host',
name: 'host',
builder: (context, state) {
return const HostGameWidget();
}),
GoRoute(
path: '/join',
name: 'join',
builder: (context, state) {
return const JoinGameWidget();
}),
GoRoute(
path: '/game',
name: 'game',
builder: (context, state) {
var args = state.extra as ChessGameArguments;
return ChessGame(
lobbyID: args.lobbyID,
playerID: args.playerID,
passphrase: args.passphrase,
);
},
)
],
);
}

View File

@ -1,10 +1,10 @@
import 'package:flutter/widgets.dart';
import 'package:mchess/connection/ws_connection.dart';
class ServerLogWidget extends StatefulWidget {
final Color textColor;
final String addString;
const ServerLogWidget(this.addString, {required this.textColor, super.key});
const ServerLogWidget({required this.textColor, super.key});
@override
State<StatefulWidget> createState() => ServerLogWidgetState();
@ -17,17 +17,11 @@ class ServerLogWidgetState extends State<ServerLogWidget> {
@override
Widget build(BuildContext context) {
log.add(widget.addString);
return SizedBox(
height: 200,
width: 200,
child: ListView(
children: [
for (int i = 0; i < log.length; i++)
Text(
style: TextStyle(color: widget.textColor, fontSize: 20), log[i])
],
),
);
// return Container();
return StreamBuilder(
stream: ServerConnection.getInstance().broadcast,
builder: (context, snapshot) {
return Container();
});
}
}

View File

@ -15,6 +15,8 @@ void main() {
// Build our app and trigger a frame.
await tester.pumpWidget(ChessGame(
playerID: UuidValue("test"),
lobbyID: UuidValue("testLobbyId"),
passphrase: 'test',
));
// Verify that our counter starts at 0.