Changes you see? To many to name them. And a splash screen for flutter web of course.
@ -2,6 +2,7 @@ import 'package:mchess/api/move.dart';
|
|||||||
|
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
move,
|
move,
|
||||||
|
invalidMove,
|
||||||
colorDetermined;
|
colorDetermined;
|
||||||
|
|
||||||
String toJson() => name;
|
String toJson() => name;
|
||||||
@ -20,9 +21,13 @@ class ApiWebsocketMessage {
|
|||||||
final MessageType type;
|
final MessageType type;
|
||||||
final ApiMove? move;
|
final ApiMove? move;
|
||||||
final ApiColor? color;
|
final ApiColor? color;
|
||||||
|
final String? reason;
|
||||||
|
|
||||||
ApiWebsocketMessage(
|
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<String, dynamic> json) {
|
factory ApiWebsocketMessage.fromJson(Map<String, dynamic> json) {
|
||||||
final type = MessageType.fromJson(json['messageType']);
|
final type = MessageType.fromJson(json['messageType']);
|
||||||
@ -30,11 +35,25 @@ class ApiWebsocketMessage {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case MessageType.colorDetermined:
|
case MessageType.colorDetermined:
|
||||||
ret = ApiWebsocketMessage(
|
ret = ApiWebsocketMessage(
|
||||||
type: type, move: null, color: ApiColor.fromJson(json['color']));
|
type: type,
|
||||||
|
move: null,
|
||||||
|
color: ApiColor.fromJson(json['color']),
|
||||||
|
reason: null);
|
||||||
break;
|
break;
|
||||||
case MessageType.move:
|
case MessageType.move:
|
||||||
ret = ApiWebsocketMessage(
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class ChessApp extends StatelessWidget {
|
|||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
routerConfig: ChessAppRouter.getInstance().router,
|
routerConfig: ChessAppRouter.getInstance().router,
|
||||||
title: 'mChess v0.1.1337',
|
title: 'mChess',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -21,8 +21,9 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
|||||||
ChessBloc._internal() : super(ChessBoardState.init()) {
|
ChessBloc._internal() : super(ChessBoardState.init()) {
|
||||||
on<InitBoard>(initBoard);
|
on<InitBoard>(initBoard);
|
||||||
on<ColorDetermined>(flipBoard);
|
on<ColorDetermined>(flipBoard);
|
||||||
on<OpponentPieceMoved>(opponentMoveHandler);
|
on<ReceivedMove>(moveHandler);
|
||||||
on<OwnPieceMoved>(ownMoveHandler);
|
on<OwnPieceMoved>(ownMoveHandler);
|
||||||
|
on<InvalidMovePlayed>(invalidMoveHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ChessBloc.getInstance() {
|
factory ChessBloc.getInstance() {
|
||||||
@ -45,8 +46,7 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
|||||||
emit(ChessBoardState(event.myColor, state.newTurnColor, state.position));
|
emit(ChessBoardState(event.myColor, state.newTurnColor, state.position));
|
||||||
}
|
}
|
||||||
|
|
||||||
void opponentMoveHandler(
|
void moveHandler(ReceivedMove event, Emitter<ChessBoardState> emit) {
|
||||||
OpponentPieceMoved event, Emitter<ChessBoardState> emit) {
|
|
||||||
log('opponentMoveHandler()');
|
log('opponentMoveHandler()');
|
||||||
ChessPosition.getInstance().recordMove(event.startSquare, event.endSquare);
|
ChessPosition.getInstance().recordMove(event.startSquare, event.endSquare);
|
||||||
var newPosition = ChessPosition.getInstance().currentPosition;
|
var newPosition = ChessPosition.getInstance().currentPosition;
|
||||||
@ -66,32 +66,37 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
|
|||||||
|
|
||||||
void ownMoveHandler(OwnPieceMoved event, Emitter<ChessBoardState> emit) {
|
void ownMoveHandler(OwnPieceMoved event, Emitter<ChessBoardState> emit) {
|
||||||
log('ownMoveHandler()');
|
log('ownMoveHandler()');
|
||||||
ChessPosition.getInstance().recordMove(event.startSquare, event.endSquare);
|
|
||||||
|
|
||||||
var start = ApiCoordinate(
|
var start = ApiCoordinate(
|
||||||
col: event.startSquare.column, row: event.startSquare.row);
|
col: event.startSquare.column, row: event.startSquare.row);
|
||||||
var end =
|
var end =
|
||||||
ApiCoordinate(col: event.endSquare.column, row: event.endSquare.row);
|
ApiCoordinate(col: event.endSquare.column, row: event.endSquare.row);
|
||||||
var move = ApiMove(startSquare: start, endSquare: end);
|
var apiMove = ApiMove(startSquare: start, endSquare: end);
|
||||||
var message =
|
var apiMessage = ApiWebsocketMessage(
|
||||||
ApiWebsocketMessage(type: MessageType.move, move: move, color: null);
|
type: MessageType.move, move: apiMove, color: null, reason: null);
|
||||||
|
|
||||||
ServerConnection.getInstance().send(jsonEncode(message));
|
ServerConnection.getInstance().send(jsonEncode(apiMessage));
|
||||||
|
|
||||||
turnColor = state.newTurnColor == ChessColor.white
|
//Temporary chess position until server responds with acknoledgement
|
||||||
? ChessColor.black
|
var move = ChessMove.fromApiMove(apiMove);
|
||||||
: ChessColor.white;
|
var tempPosition = ChessPosition.getInstance().copyOfCurrentPosition;
|
||||||
|
tempPosition[move.to] = tempPosition[move.from] ?? const ChessPiece.none();
|
||||||
var newPosition = ChessPosition.getInstance().currentPosition;
|
tempPosition[move.from] = const ChessPiece.none();
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
ChessBoardState(
|
ChessBoardState(
|
||||||
state.bottomColor,
|
state.bottomColor,
|
||||||
turnColor,
|
turnColor,
|
||||||
newPosition,
|
tempPosition,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void invalidMoveHandler(
|
||||||
|
InvalidMovePlayed event, Emitter<ChessBoardState> emit) {
|
||||||
|
emit(ChessBoardState(state.bottomColor, turnColor,
|
||||||
|
ChessPosition.getInstance().currentPosition));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChessBoardState {
|
class ChessBoardState {
|
||||||
|
@ -2,11 +2,11 @@ import 'package:mchess/utils/chess_utils.dart';
|
|||||||
|
|
||||||
abstract class ChessEvent {}
|
abstract class ChessEvent {}
|
||||||
|
|
||||||
class OpponentPieceMoved extends ChessEvent {
|
class ReceivedMove extends ChessEvent {
|
||||||
final ChessCoordinate startSquare;
|
final ChessCoordinate startSquare;
|
||||||
final ChessCoordinate endSquare;
|
final ChessCoordinate endSquare;
|
||||||
|
|
||||||
OpponentPieceMoved({required this.startSquare, required this.endSquare});
|
ReceivedMove({required this.startSquare, required this.endSquare});
|
||||||
}
|
}
|
||||||
|
|
||||||
class OwnPieceMoved extends ChessEvent {
|
class OwnPieceMoved extends ChessEvent {
|
||||||
@ -25,3 +25,9 @@ class ColorDetermined extends ChessEvent {
|
|||||||
|
|
||||||
ColorDetermined({required this.myColor});
|
ColorDetermined({required this.myColor});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InvalidMovePlayed extends ChessEvent {
|
||||||
|
final ChessMove move;
|
||||||
|
|
||||||
|
InvalidMovePlayed({required this.move});
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ class ChessPosition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChessPositionType get currentPosition => position;
|
ChessPositionType get currentPosition => position;
|
||||||
|
ChessPositionType get copyOfCurrentPosition => Map.from(position);
|
||||||
ChessMove? get lastMove {
|
ChessMove? get lastMove {
|
||||||
if (history.isEmpty) return null;
|
if (history.isEmpty) return null;
|
||||||
return history.last;
|
return history.last;
|
||||||
@ -103,7 +104,7 @@ class ChessPosition {
|
|||||||
logString = '$logString\n';
|
logString = '$logString\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
log(logString);
|
print(logString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void logHistory(ChessMoveHistory hist) {
|
void logHistory(ChessMoveHistory hist) {
|
||||||
|
@ -69,6 +69,9 @@ class ServerConnection {
|
|||||||
case MessageType.move:
|
case MessageType.move:
|
||||||
handleIncomingMoveMessage(apiMessage);
|
handleIncomingMoveMessage(apiMessage);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MessageType.invalidMove:
|
||||||
|
handleInvalidMoveMessage(apiMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,11 +82,13 @@ class ServerConnection {
|
|||||||
|
|
||||||
void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) {
|
void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) {
|
||||||
var move = ChessMove.fromApiMove(apiMessage.move!);
|
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()
|
ChessBloc.getInstance()
|
||||||
.add(OpponentPieceMoved(startSquare: move.from, endSquare: move.to));
|
.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!)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
pubspec.lock
@ -144,14 +144,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
js:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: js
|
|
||||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.6.7"
|
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -341,6 +333,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
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:
|
web_socket_channel:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -358,5 +358,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.0 <4.0.0"
|
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||||
flutter: ">=3.7.0-0"
|
flutter: ">=3.7.0-0"
|
||||||
|
Before Width: | Height: | Size: 5.2 KiB |
@ -24,7 +24,7 @@
|
|||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="apple-mobile-web-app-title" content="mchess">
|
<meta name="apple-mobile-web-app-title" content="mchess">
|
||||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
<link rel="apple-touch-icon" href="web_icons/Icon-192.png">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
@ -40,20 +40,85 @@
|
|||||||
<script src="flutter.js" defer></script>
|
<script src="flutter.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script>
|
</script>
|
||||||
window.addEventListener('load', function(ev) {
|
|
||||||
// Download main.dart.js
|
<!-- Loading indicator -->
|
||||||
|
<div id="loading">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
inset: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading img {
|
||||||
|
animation: 1s ease-in-out 0s infinite alternate breathe;
|
||||||
|
opacity: .66;
|
||||||
|
transition: opacity .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading.main_done img {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading.init_done img {
|
||||||
|
animation: .33s ease-in-out 0s 1 forwards zooooom;
|
||||||
|
opacity: .05;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes breathe {
|
||||||
|
from {
|
||||||
|
transform: scale(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: scale(0.95)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes zooooom {
|
||||||
|
from {
|
||||||
|
transform: scale(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: scale(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<img src="web_icons/Icon-192.png" alt="Loading indicator..." />
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
var loading = document.querySelector('#loading');
|
||||||
_flutter.loader.loadEntrypoint({
|
_flutter.loader.loadEntrypoint({
|
||||||
serviceWorker: {
|
serviceWorker: {
|
||||||
serviceWorkerVersion: serviceWorkerVersion,
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
},
|
|
||||||
onEntrypointLoaded: function(engineInitializer) {
|
|
||||||
engineInitializer.initializeEngine().then(function(appRunner) {
|
|
||||||
appRunner.runApp();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}).then(function (engineInitializer) {
|
||||||
|
loading.classList.add('main_done');
|
||||||
|
return engineInitializer.initializeEngine();
|
||||||
|
}).then(function (appRunner) {
|
||||||
|
loading.classList.add('init_done');
|
||||||
|
return appRunner.runApp();
|
||||||
|
}).then(function (app) {
|
||||||
|
// Wait a few milliseconds so users can see the "zoooom" animation
|
||||||
|
// before getting rid of the "loading" div.
|
||||||
|
window.setTimeout(function () {
|
||||||
|
loading.remove();
|
||||||
|
}, 200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -10,23 +10,23 @@
|
|||||||
"prefer_related_applications": false,
|
"prefer_related_applications": false,
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "icons/Icon-192.png",
|
"src": "web_icons/Icon-192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/Icon-512.png",
|
"src": "web_icons/Icon-512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/Icon-maskable-192.png",
|
"src": "web_icons/Icon-maskable-192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "maskable"
|
"purpose": "maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "icons/Icon-maskable-512.png",
|
"src": "web_icons/Icon-maskable-512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "maskable"
|
"purpose": "maskable"
|
||||||
|
BIN
web/web_icons/Icon-192.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |