From 43fca47dae9b320561b0e61f7dd7cef3ec3618d0 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 28 Jun 2023 12:37:59 +0200 Subject: [PATCH] Changes you see? To many to name them. And a splash screen for flutter web of course. --- lib/api/websocket_message.dart | 25 ++++- lib/chess/chess_app.dart | 2 +- lib/chess_bloc/chess_bloc.dart | 33 +++--- lib/chess_bloc/chess_events.dart | 10 +- lib/chess_bloc/chess_position.dart | 3 +- lib/connection/ws_connection.dart | 17 +-- pubspec.lock | 18 ++-- web/icons/Icon-192.png | Bin 5292 -> 0 bytes web/index.html | 97 +++++++++++++++--- web/manifest.json | 8 +- web/web_icons/Icon-192.png | Bin 0 -> 7505 bytes web/{icons => web_icons}/Icon-512.png | Bin .../Icon-maskable-192.png | Bin .../Icon-maskable-512.png | Bin 14 files changed, 157 insertions(+), 56 deletions(-) delete mode 100644 web/icons/Icon-192.png create mode 100644 web/web_icons/Icon-192.png rename web/{icons => web_icons}/Icon-512.png (100%) rename web/{icons => web_icons}/Icon-maskable-192.png (100%) rename web/{icons => web_icons}/Icon-maskable-512.png (100%) diff --git a/lib/api/websocket_message.dart b/lib/api/websocket_message.dart index 19cfd2c..23f559b 100644 --- a/lib/api/websocket_message.dart +++ b/lib/api/websocket_message.dart @@ -2,6 +2,7 @@ import 'package:mchess/api/move.dart'; enum MessageType { move, + invalidMove, colorDetermined; String toJson() => name; @@ -20,9 +21,13 @@ class ApiWebsocketMessage { final MessageType type; final ApiMove? move; final ApiColor? color; + final String? reason; ApiWebsocketMessage( - {required this.type, required this.move, required this.color}); + {required this.type, + required this.move, + required this.color, + required this.reason}); factory ApiWebsocketMessage.fromJson(Map json) { final type = MessageType.fromJson(json['messageType']); @@ -30,11 +35,25 @@ class ApiWebsocketMessage { switch (type) { case MessageType.colorDetermined: ret = ApiWebsocketMessage( - type: type, move: null, color: ApiColor.fromJson(json['color'])); + type: type, + move: null, + color: ApiColor.fromJson(json['color']), + reason: null); break; case MessageType.move: ret = ApiWebsocketMessage( - type: type, move: ApiMove.fromJson(json['move']), color: null); + type: type, + move: ApiMove.fromJson(json['move']), + color: null, + reason: null); + break; + case MessageType.invalidMove: + ret = ApiWebsocketMessage( + type: type, + move: ApiMove.fromJson(json['move']), + color: null, + reason: json['reason'], + ); } return ret; } diff --git a/lib/chess/chess_app.dart b/lib/chess/chess_app.dart index c1dacdd..fa0260c 100644 --- a/lib/chess/chess_app.dart +++ b/lib/chess/chess_app.dart @@ -18,7 +18,7 @@ class ChessApp extends StatelessWidget { useMaterial3: true, ), routerConfig: ChessAppRouter.getInstance().router, - title: 'mChess v0.1.1337', + title: 'mChess', ), ), ); diff --git a/lib/chess_bloc/chess_bloc.dart b/lib/chess_bloc/chess_bloc.dart index cdc45b0..fc459e5 100644 --- a/lib/chess_bloc/chess_bloc.dart +++ b/lib/chess_bloc/chess_bloc.dart @@ -21,8 +21,9 @@ class ChessBloc extends Bloc { ChessBloc._internal() : super(ChessBoardState.init()) { on(initBoard); on(flipBoard); - on(opponentMoveHandler); + on(moveHandler); on(ownMoveHandler); + on(invalidMoveHandler); } factory ChessBloc.getInstance() { @@ -45,8 +46,7 @@ class ChessBloc extends Bloc { emit(ChessBoardState(event.myColor, state.newTurnColor, state.position)); } - void opponentMoveHandler( - OpponentPieceMoved event, Emitter emit) { + void moveHandler(ReceivedMove event, Emitter emit) { log('opponentMoveHandler()'); ChessPosition.getInstance().recordMove(event.startSquare, event.endSquare); var newPosition = ChessPosition.getInstance().currentPosition; @@ -66,32 +66,37 @@ class ChessBloc extends Bloc { void ownMoveHandler(OwnPieceMoved event, Emitter emit) { log('ownMoveHandler()'); - ChessPosition.getInstance().recordMove(event.startSquare, event.endSquare); var start = ApiCoordinate( col: event.startSquare.column, row: event.startSquare.row); var end = ApiCoordinate(col: event.endSquare.column, row: event.endSquare.row); - var move = ApiMove(startSquare: start, endSquare: end); - var message = - ApiWebsocketMessage(type: MessageType.move, move: move, color: null); + var apiMove = ApiMove(startSquare: start, endSquare: end); + var apiMessage = ApiWebsocketMessage( + type: MessageType.move, move: apiMove, color: null, reason: null); - ServerConnection.getInstance().send(jsonEncode(message)); + ServerConnection.getInstance().send(jsonEncode(apiMessage)); - turnColor = state.newTurnColor == ChessColor.white - ? ChessColor.black - : ChessColor.white; - - var newPosition = ChessPosition.getInstance().currentPosition; + //Temporary chess position until server responds with acknoledgement + var move = ChessMove.fromApiMove(apiMove); + var tempPosition = ChessPosition.getInstance().copyOfCurrentPosition; + tempPosition[move.to] = tempPosition[move.from] ?? const ChessPiece.none(); + tempPosition[move.from] = const ChessPiece.none(); emit( ChessBoardState( state.bottomColor, turnColor, - newPosition, + tempPosition, ), ); } + + void invalidMoveHandler( + InvalidMovePlayed event, Emitter emit) { + emit(ChessBoardState(state.bottomColor, turnColor, + ChessPosition.getInstance().currentPosition)); + } } class ChessBoardState { diff --git a/lib/chess_bloc/chess_events.dart b/lib/chess_bloc/chess_events.dart index a7becb1..171c4e1 100644 --- a/lib/chess_bloc/chess_events.dart +++ b/lib/chess_bloc/chess_events.dart @@ -2,11 +2,11 @@ import 'package:mchess/utils/chess_utils.dart'; abstract class ChessEvent {} -class OpponentPieceMoved extends ChessEvent { +class ReceivedMove extends ChessEvent { final ChessCoordinate startSquare; final ChessCoordinate endSquare; - OpponentPieceMoved({required this.startSquare, required this.endSquare}); + ReceivedMove({required this.startSquare, required this.endSquare}); } class OwnPieceMoved extends ChessEvent { @@ -25,3 +25,9 @@ class ColorDetermined extends ChessEvent { ColorDetermined({required this.myColor}); } + +class InvalidMovePlayed extends ChessEvent { + final ChessMove move; + + InvalidMovePlayed({required this.move}); +} diff --git a/lib/chess_bloc/chess_position.dart b/lib/chess_bloc/chess_position.dart index 2133a04..aaa3065 100644 --- a/lib/chess_bloc/chess_position.dart +++ b/lib/chess_bloc/chess_position.dart @@ -20,6 +20,7 @@ class ChessPosition { } ChessPositionType get currentPosition => position; + ChessPositionType get copyOfCurrentPosition => Map.from(position); ChessMove? get lastMove { if (history.isEmpty) return null; return history.last; @@ -103,7 +104,7 @@ class ChessPosition { logString = '$logString\n'; } - log(logString); + print(logString); } void logHistory(ChessMoveHistory hist) { diff --git a/lib/connection/ws_connection.dart b/lib/connection/ws_connection.dart index d129aa3..92690a6 100644 --- a/lib/connection/ws_connection.dart +++ b/lib/connection/ws_connection.dart @@ -69,6 +69,9 @@ class ServerConnection { case MessageType.move: handleIncomingMoveMessage(apiMessage); break; + + case MessageType.invalidMove: + handleInvalidMoveMessage(apiMessage); } } @@ -79,11 +82,13 @@ class ServerConnection { void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) { var move = ChessMove.fromApiMove(apiMessage.move!); - if (move == ChessPosition.getInstance().lastMove) { - //This is our own move that got resent by the server. Do not process. - } else { - ChessBloc.getInstance() - .add(OpponentPieceMoved(startSquare: move.from, endSquare: move.to)); - } + ChessBloc.getInstance() + .add(ReceivedMove(startSquare: move.from, endSquare: move.to)); + } + + void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) { + log("invalid move message received, with move: ${apiMessage.move.toString()}"); + ChessBloc.getInstance() + .add(InvalidMovePlayed(move: ChessMove.fromApiMove(apiMessage.move!))); } } diff --git a/pubspec.lock b/pubspec.lock index 9c0d7d9..7aa76cc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -144,14 +144,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" lints: dependency: transitive description: @@ -341,6 +333,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: "direct main" description: @@ -358,5 +358,5 @@ packages: source: hosted version: "6.3.0" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.7.0-0" diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png deleted file mode 100644 index b749bfef07473333cf1dd31e9eed89862a5d52aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5292 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 diff --git a/web/index.html b/web/index.html index 4bca956..1f6d9d5 100644 --- a/web/index.html +++ b/web/index.html @@ -24,7 +24,7 @@ - + @@ -40,20 +40,85 @@ - + + + +
+ + Loading indicator... +
+ diff --git a/web/manifest.json b/web/manifest.json index 082697a..cffd6a6 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -10,23 +10,23 @@ "prefer_related_applications": false, "icons": [ { - "src": "icons/Icon-192.png", + "src": "web_icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "icons/Icon-512.png", + "src": "web_icons/Icon-512.png", "sizes": "512x512", "type": "image/png" }, { - "src": "icons/Icon-maskable-192.png", + "src": "web_icons/Icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "icons/Icon-maskable-512.png", + "src": "web_icons/Icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/web/web_icons/Icon-192.png b/web/web_icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..2dc8e535698bf2e84ac3d44635f064f3a0af67f9 GIT binary patch literal 7505 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 literal 0 HcmV?d00001 diff --git a/web/icons/Icon-512.png b/web/web_icons/Icon-512.png similarity index 100% rename from web/icons/Icon-512.png rename to web/web_icons/Icon-512.png diff --git a/web/icons/Icon-maskable-192.png b/web/web_icons/Icon-maskable-192.png similarity index 100% rename from web/icons/Icon-maskable-192.png rename to web/web_icons/Icon-maskable-192.png diff --git a/web/icons/Icon-maskable-512.png b/web/web_icons/Icon-maskable-512.png similarity index 100% rename from web/icons/Icon-maskable-512.png rename to web/web_icons/Icon-maskable-512.png