Handle board status message
This is another step to allow reconnecting after connection loss or browser closing. When the game is left with the X button on the bottom right, we will close the websocket connection, to let the server know, that we are gone. The server still has issues that prevent this from working flawlessly.
This commit is contained in:
parent
e441aaec1e
commit
1cb5ffb82b
@ -12,8 +12,8 @@ class PlayerInfo {
|
||||
});
|
||||
|
||||
factory PlayerInfo.fromJson(Map<String, dynamic> json) {
|
||||
final playerid = UuidValue(json['playerID']);
|
||||
final lobbyid = UuidValue(json['lobbyID']);
|
||||
final playerid = UuidValue.fromString(json['playerID']);
|
||||
final lobbyid = UuidValue.fromString(json['lobbyID']);
|
||||
final passphrase = json['passphrase'];
|
||||
|
||||
return PlayerInfo(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:mchess/api/move.dart';
|
||||
|
||||
enum MessageType {
|
||||
boardState,
|
||||
move,
|
||||
invalidMove,
|
||||
colorDetermined;
|
||||
@ -20,7 +21,8 @@ enum ApiColor {
|
||||
class ApiWebsocketMessage {
|
||||
final MessageType type;
|
||||
final ApiMove? move;
|
||||
final ApiColor? color;
|
||||
final ApiColor? turnColor;
|
||||
final ApiColor? playerColor;
|
||||
final String? reason;
|
||||
final String? position;
|
||||
final ApiCoordinate? squareInCheck;
|
||||
@ -28,7 +30,8 @@ class ApiWebsocketMessage {
|
||||
ApiWebsocketMessage({
|
||||
required this.type,
|
||||
required this.move,
|
||||
required this.color,
|
||||
required this.turnColor,
|
||||
required this.playerColor,
|
||||
required this.reason,
|
||||
required this.position,
|
||||
required this.squareInCheck,
|
||||
@ -38,34 +41,46 @@ class ApiWebsocketMessage {
|
||||
final type = MessageType.fromJson(json['messageType']);
|
||||
ApiWebsocketMessage ret;
|
||||
switch (type) {
|
||||
case MessageType.boardState:
|
||||
ret = ApiWebsocketMessage(
|
||||
type: type,
|
||||
move: null,
|
||||
turnColor: ApiColor.fromJson(json['turnColor']),
|
||||
reason: null,
|
||||
position: json['position'],
|
||||
squareInCheck: null,
|
||||
playerColor: ApiColor.fromJson(json['playerColor']),
|
||||
);
|
||||
case MessageType.colorDetermined:
|
||||
ret = ApiWebsocketMessage(
|
||||
type: type,
|
||||
move: null,
|
||||
color: ApiColor.fromJson(json['color']),
|
||||
turnColor: null,
|
||||
reason: null,
|
||||
position: null,
|
||||
squareInCheck: null,
|
||||
playerColor: ApiColor.fromJson(json['playerColor']),
|
||||
);
|
||||
break;
|
||||
case MessageType.move:
|
||||
ret = ApiWebsocketMessage(
|
||||
type: type,
|
||||
move: ApiMove.fromJson(json['move']),
|
||||
color: null,
|
||||
reason: null,
|
||||
position: json['position'],
|
||||
squareInCheck: json['squareInCheck']
|
||||
);
|
||||
type: type,
|
||||
move: ApiMove.fromJson(json['move']),
|
||||
turnColor: null,
|
||||
reason: null,
|
||||
position: json['position'],
|
||||
squareInCheck: json['squareInCheck'],
|
||||
playerColor: null);
|
||||
break;
|
||||
case MessageType.invalidMove:
|
||||
ret = ApiWebsocketMessage(
|
||||
type: type,
|
||||
move: ApiMove.fromJson(json['move']),
|
||||
color: null,
|
||||
turnColor: null,
|
||||
reason: json['reason'],
|
||||
position: null,
|
||||
squareInCheck: json['squareInCheck'],
|
||||
playerColor: null,
|
||||
);
|
||||
}
|
||||
return ret;
|
||||
@ -74,6 +89,6 @@ class ApiWebsocketMessage {
|
||||
Map<String, dynamic> toJson() => {
|
||||
'messageType': type,
|
||||
'move': move,
|
||||
'color': color,
|
||||
'color': turnColor,
|
||||
};
|
||||
}
|
||||
|
@ -68,9 +68,10 @@ class _ChessSquareState extends State<ChessSquare> {
|
||||
return true;
|
||||
},
|
||||
listener: (context, state) {
|
||||
setState(() {
|
||||
squareColor = Colors.red;
|
||||
});},
|
||||
setState(() {
|
||||
squareColor = Colors.red;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
color: widget.color,
|
||||
child: ChessSquareOuterDragTarget(
|
||||
|
@ -20,10 +20,11 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
||||
ChessBloc._internal() : super(ChessBoardState.init()) {
|
||||
on<InitBoard>(initBoard);
|
||||
on<ColorDetermined>(flipBoard);
|
||||
on<ReceivedMove>(moveAndPositionHandler);
|
||||
on<ReceivedBoardState>(moveAndPositionHandler);
|
||||
on<OwnPieceMoved>(ownMoveHandler);
|
||||
on<OwnPromotionPlayed>(ownPromotionHandler);
|
||||
on<InvalidMovePlayed>(invalidMoveHandler);
|
||||
on<BoardStatusReceived>(boardStatusHandler);
|
||||
}
|
||||
|
||||
factory ChessBloc.getInstance() {
|
||||
@ -65,21 +66,24 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
||||
}
|
||||
|
||||
void moveAndPositionHandler(
|
||||
ReceivedMove event,
|
||||
Emitter<ChessBoardState> emit,
|
||||
) {
|
||||
ChessPositionManager.getInstance()
|
||||
.recordMove(event.startSquare, event.endSquare, event.position);
|
||||
ReceivedBoardState event, Emitter<ChessBoardState> emit) {
|
||||
turnColor = state.newTurnColor == ChessColor.white
|
||||
? ChessColor.black
|
||||
: ChessColor.white;
|
||||
|
||||
ChessMove? move;
|
||||
if (event.startSquare != null && event.endSquare != null) {
|
||||
move = ChessMove(from: event.startSquare!, to: event.endSquare!);
|
||||
ChessPositionManager.getInstance()
|
||||
.recordMove(event.startSquare, event.endSquare, event.position);
|
||||
}
|
||||
|
||||
emit(
|
||||
ChessBoardState(
|
||||
state.bottomColor,
|
||||
turnColor,
|
||||
event.position,
|
||||
ChessMove(from: event.startSquare, to: event.endSquare),
|
||||
move,
|
||||
true,
|
||||
event.squareInCheck,
|
||||
),
|
||||
@ -93,10 +97,11 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
||||
var apiMessage = ApiWebsocketMessage(
|
||||
type: MessageType.move,
|
||||
move: apiMove,
|
||||
color: null,
|
||||
turnColor: null,
|
||||
reason: null,
|
||||
position: null,
|
||||
squareInCheck: null,
|
||||
playerColor: null,
|
||||
);
|
||||
|
||||
ServerConnection.getInstance().send(jsonEncode(apiMessage));
|
||||
@ -122,10 +127,11 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
||||
var message = ApiWebsocketMessage(
|
||||
type: MessageType.move,
|
||||
move: apiMove,
|
||||
color: null,
|
||||
turnColor: null,
|
||||
reason: null,
|
||||
position: null,
|
||||
squareInCheck: null,
|
||||
playerColor: null,
|
||||
);
|
||||
log(jsonEncode(message));
|
||||
ServerConnection.getInstance().send(jsonEncode(message));
|
||||
@ -144,6 +150,20 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void boardStatusHandler(
|
||||
BoardStatusReceived event, Emitter<ChessBoardState> emit) {
|
||||
emit(
|
||||
ChessBoardState(
|
||||
event.myColor,
|
||||
event.whoseTurn,
|
||||
event.pos,
|
||||
ChessMove.none(),
|
||||
false,
|
||||
ChessCoordinate.none(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChessBoardState {
|
||||
@ -167,12 +187,12 @@ class ChessBoardState {
|
||||
ChessColor bottomColor,
|
||||
ChessColor turnColor,
|
||||
ChessPosition position,
|
||||
ChessMove lastMove,
|
||||
ChessMove? lastMove,
|
||||
bool positionAckd,
|
||||
ChessCoordinate squareInCheck,
|
||||
) {
|
||||
return ChessBoardState._(bottomColor, turnColor, position, lastMove,
|
||||
positionAckd, squareInCheck);
|
||||
return ChessBoardState._(bottomColor, turnColor, position,
|
||||
lastMove ?? ChessMove.none(), positionAckd, squareInCheck);
|
||||
}
|
||||
|
||||
factory ChessBoardState.init() {
|
||||
|
@ -3,13 +3,13 @@ import 'package:mchess/utils/chess_utils.dart';
|
||||
|
||||
abstract class ChessEvent {}
|
||||
|
||||
class ReceivedMove extends ChessEvent {
|
||||
final ChessCoordinate startSquare;
|
||||
final ChessCoordinate endSquare;
|
||||
class ReceivedBoardState extends ChessEvent {
|
||||
final ChessCoordinate? startSquare;
|
||||
final ChessCoordinate? endSquare;
|
||||
final ChessPosition position;
|
||||
final ChessCoordinate squareInCheck;
|
||||
|
||||
ReceivedMove({
|
||||
ReceivedBoardState({
|
||||
required this.startSquare,
|
||||
required this.endSquare,
|
||||
required this.position,
|
||||
@ -54,3 +54,15 @@ class InvalidMovePlayed extends ChessEvent {
|
||||
required this.squareInCheck,
|
||||
});
|
||||
}
|
||||
|
||||
class BoardStatusReceived extends ChessEvent {
|
||||
final ChessPosition pos;
|
||||
final ChessColor myColor;
|
||||
final ChessColor whoseTurn;
|
||||
|
||||
BoardStatusReceived({
|
||||
required this.pos,
|
||||
required this.myColor,
|
||||
required this.whoseTurn,
|
||||
});
|
||||
}
|
||||
|
@ -71,10 +71,16 @@ class ChessPositionManager {
|
||||
log(logString);
|
||||
}
|
||||
|
||||
void recordMove(ChessCoordinate from, ChessCoordinate to, ChessPosition pos) {
|
||||
void recordMove(
|
||||
ChessCoordinate? from, ChessCoordinate? to, ChessPosition pos) {
|
||||
position = pos;
|
||||
|
||||
history.add(ChessMove(from: from, to: to));
|
||||
history.add(
|
||||
ChessMove(
|
||||
from: from ?? ChessCoordinate.none(),
|
||||
to: to ?? ChessCoordinate.none(),
|
||||
),
|
||||
);
|
||||
|
||||
logPosition(position);
|
||||
logHistory(history);
|
||||
|
@ -12,9 +12,8 @@ import 'package:mchess/utils/chess_utils.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
class ServerConnection {
|
||||
late WebSocketChannel channel;
|
||||
WebSocketChannel? channel;
|
||||
late bool wasConnected = false;
|
||||
late int counter = 0;
|
||||
Stream broadcast = const Stream.empty();
|
||||
|
||||
static final ServerConnection _instance = ServerConnection._internal();
|
||||
@ -32,18 +31,22 @@ class ServerConnection {
|
||||
}
|
||||
|
||||
void send(String message) {
|
||||
channel.sink.add(message);
|
||||
counter++;
|
||||
if (channel == null) {
|
||||
log("Sending on channel without initializing");
|
||||
return;
|
||||
}
|
||||
channel!.sink.add(message);
|
||||
}
|
||||
|
||||
void connect(String playerID, lobbyID, String? passphrase) {
|
||||
String url;
|
||||
if (kDebugMode) {
|
||||
channel =
|
||||
WebSocketChannel.connect(Uri.parse('ws://localhost:8080/api/ws'));
|
||||
url = 'ws://localhost:8080/api/ws';
|
||||
} else {
|
||||
channel = WebSocketChannel.connect(
|
||||
Uri.parse('wss://chess.sw-gross.de:9999/api/ws'));
|
||||
url = 'wss://chess.sw-gross.de:9999/api/ws';
|
||||
}
|
||||
channel = WebSocketChannel.connect(Uri.parse(url));
|
||||
|
||||
send(
|
||||
jsonEncode(
|
||||
WebsocketMessageIdentifyPlayer(
|
||||
@ -54,17 +57,26 @@ class ServerConnection {
|
||||
),
|
||||
);
|
||||
|
||||
log(channel.closeCode.toString());
|
||||
broadcast = channel.stream.asBroadcastStream();
|
||||
log(channel!.closeCode.toString());
|
||||
broadcast = channel!.stream.asBroadcastStream();
|
||||
broadcast.listen(handleIncomingData);
|
||||
}
|
||||
|
||||
void disconnectExistingConnection() {
|
||||
if (channel == null) return;
|
||||
channel!.sink.close();
|
||||
}
|
||||
|
||||
void handleIncomingData(dynamic data) {
|
||||
log("Data received:");
|
||||
log(data);
|
||||
var apiMessage = ApiWebsocketMessage.fromJson(jsonDecode(data));
|
||||
|
||||
switch (apiMessage.type) {
|
||||
case MessageType.boardState:
|
||||
handleBoardStateMessage(apiMessage);
|
||||
break;
|
||||
|
||||
case MessageType.colorDetermined:
|
||||
handleIncomingColorDeterminedMessage(apiMessage);
|
||||
break;
|
||||
@ -78,25 +90,46 @@ class ServerConnection {
|
||||
}
|
||||
}
|
||||
|
||||
void handleIncomingColorDeterminedMessage(ApiWebsocketMessage apiMessage) {
|
||||
ConnectionCubit.getInstance().opponentConnected();
|
||||
ChessBloc.getInstance().add(InitBoard());
|
||||
ChessBloc.getInstance().add(
|
||||
ColorDetermined(myColor: ChessColor.fromApiColor(apiMessage.color!)));
|
||||
}
|
||||
|
||||
void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) {
|
||||
var move = ChessMove.fromApiMove(apiMessage.move!);
|
||||
void handleBoardStateMessage(ApiWebsocketMessage apiMessage) {
|
||||
ChessMove? move;
|
||||
if (apiMessage.move != null) {
|
||||
move = ChessMove.fromApiMove(apiMessage.move!);
|
||||
}
|
||||
|
||||
if (apiMessage.position != null) {
|
||||
ChessBloc.getInstance().add(
|
||||
ReceivedMove(
|
||||
ReceivedBoardState(
|
||||
startSquare: move?.from,
|
||||
endSquare: move?.to,
|
||||
position: ChessPositionManager.getInstance()
|
||||
.fromPGNString(apiMessage.position!),
|
||||
squareInCheck: ChessCoordinate.fromApiCoordinate(
|
||||
apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log('Error: no position received');
|
||||
}
|
||||
}
|
||||
|
||||
void handleIncomingColorDeterminedMessage(ApiWebsocketMessage apiMessage) {
|
||||
ConnectionCubit.getInstance().opponentConnected();
|
||||
ChessBloc.getInstance().add(InitBoard());
|
||||
ChessBloc.getInstance().add(ColorDetermined(
|
||||
myColor: ChessColor.fromApiColor(apiMessage.playerColor!)));
|
||||
}
|
||||
|
||||
void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) {
|
||||
if (apiMessage.position != null) {
|
||||
var move = ChessMove.fromApiMove(apiMessage.move!);
|
||||
ChessBloc.getInstance().add(
|
||||
ReceivedBoardState(
|
||||
startSquare: move.from,
|
||||
endSquare: move.to,
|
||||
position: ChessPositionManager.getInstance()
|
||||
.fromPGNString(apiMessage.position!),
|
||||
squareInCheck:
|
||||
ChessCoordinate.fromApiCoordinate(apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
|
||||
squareInCheck: ChessCoordinate.fromApiCoordinate(
|
||||
apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@ -109,8 +142,8 @@ class ServerConnection {
|
||||
ChessBloc.getInstance().add(
|
||||
InvalidMovePlayed(
|
||||
move: ChessMove.fromApiMove(apiMessage.move!),
|
||||
squareInCheck:
|
||||
ChessCoordinate.fromApiCoordinate(apiMessage.squareInCheck!),
|
||||
squareInCheck: ChessCoordinate.fromApiCoordinate(
|
||||
apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import 'package:mchess/chess_bloc/chess_bloc.dart';
|
||||
|
||||
import 'package:mchess/chess/chess_board.dart';
|
||||
import 'package:mchess/chess_bloc/promotion_bloc.dart';
|
||||
import 'package:mchess/connection/ws_connection.dart';
|
||||
import 'package:mchess/utils/chess_utils.dart';
|
||||
import 'package:mchess/utils/widgets/promotion_dialog.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
@ -53,6 +54,7 @@ class _ChessGameState extends State<ChessGame> {
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
ServerConnection.getInstance().disconnectExistingConnection();
|
||||
context.push('/');
|
||||
},
|
||||
child: const Icon(Icons.cancel),
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:mchess/connection/ws_connection.dart';
|
||||
import 'package:mchess/pages/chess_game.dart';
|
||||
import 'package:mchess/pages/join_game.dart';
|
||||
import 'package:mchess/pages/lobby_selector.dart';
|
||||
@ -19,7 +20,9 @@ class ChessAppRouter {
|
||||
GoRoute(
|
||||
path: '/',
|
||||
name: 'lobbySelector',
|
||||
builder: (context, state) => const LobbySelector(),
|
||||
builder: (context, state) {
|
||||
return const LobbySelector();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/prepareRandom',
|
||||
|
@ -1,53 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:mchess/chess_bloc/chess_bloc.dart';
|
||||
import 'package:mchess/chess_bloc/chess_position.dart';
|
||||
import 'package:mchess/utils/chess_utils.dart';
|
||||
|
||||
class MoveHistory extends StatefulWidget {
|
||||
const MoveHistory({super.key});
|
||||
|
||||
@override
|
||||
State<MoveHistory> createState() => _MoveHistoryState();
|
||||
}
|
||||
|
||||
class _MoveHistoryState extends State<MoveHistory> {
|
||||
late List<String> entries;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
entries = [];
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<ChessBloc, ChessBoardState>(
|
||||
listener: (context, state) {
|
||||
List<String> newEntries = [];
|
||||
var positionManager = ChessPositionManager.getInstance();
|
||||
var allMoves = positionManager.allMoves;
|
||||
|
||||
for (ChessMove move in allMoves) {
|
||||
var movedPiece = positionManager.getPieceAt(move.to);
|
||||
var char = pieceCharacter[ChessPieceAssetKey(
|
||||
pieceClass: movedPiece!.pieceClass,
|
||||
color: movedPiece.color.getOpposite())];
|
||||
|
||||
if (movedPiece.color == ChessColor.white) {
|
||||
newEntries.add("$char ${move.to.toAlphabetical()}");
|
||||
} else {
|
||||
newEntries.last =
|
||||
"${newEntries.last}\t\t$char${move.to.toAlphabetical()}";
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
entries = newEntries;
|
||||
});
|
||||
},
|
||||
child: ListView(children: [
|
||||
for (var entry in entries) Text(entry),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
20
pubspec.lock
20
pubspec.lock
@ -124,18 +124,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: c247a4f76071c3b97bb5ae8912968870d5565644801c5e09f3bc961b4d874895
|
||||
sha256: c5fa45fa502ee880839e3b2152d987c44abae26d064a2376d4aad434cf0f7b15
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.1.1"
|
||||
version: "12.1.3"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -212,10 +212,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
version: "6.0.2"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -361,10 +361,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.2"
|
||||
version: "6.5.0"
|
||||
sdks:
|
||||
dart: ">=3.2.0-194.0.dev <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
dart: ">=3.2.0 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
|
@ -13,9 +13,9 @@ import 'package:uuid/uuid.dart';
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const ChessGame(
|
||||
playerID: UuidValue("test"),
|
||||
lobbyID: UuidValue("testLobbyId"),
|
||||
await tester.pumpWidget(ChessGame(
|
||||
playerID: UuidValue.fromString("test"),
|
||||
lobbyID: UuidValue.fromString("testLobbyId"),
|
||||
passphrase: 'test',
|
||||
));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user