Compare commits

...

48 Commits

Author SHA1 Message Date
ae087a1d56 bump version and flutter pub upgrade 2024-06-27 21:57:19 +02:00
dfc7b156f1 fix handling errors 2024-06-27 21:55:54 +02:00
e5a04b02ac flutter pub upgrade 2024-05-22 00:05:14 +02:00
9c0ff492c5 Merge pull request 'Fix rejoining' (#13) from fix-rejoining into master
Reviewed-on: #13
2024-05-21 21:56:35 +00:00
fa525c2442 bump version 2024-05-21 23:47:50 +02:00
bde3d3e358 url 2024-05-21 23:47:11 +02:00
358e8a6041 more changes because it's fun 2024-05-21 18:48:06 +02:00
2a2e219c80 Wait for websocket to be disconnected before continuing 2024-05-20 17:21:25 +02:00
adf8c86692 Make many changes
1. A game is only identified by a passphrase (not a lobby id)
2. We can store multiple passphrase/playerID combinations
2024-05-20 15:34:20 +02:00
a10db3e2a2 Merge pull request 'Make games reloadable' (#12) from simplify-flow into master
Reviewed-on: #12
2024-05-19 19:48:27 +00:00
9bbde2927b Simplify flow and allow site reloads 2024-05-19 21:44:33 +02:00
c802251c9d fix url 2024-05-19 14:46:18 +02:00
e4d4b81cba flutter pub upgrade 2024-05-19 14:44:06 +02:00
d924341742 Merge pull request 'rejoinable-game' (#11) from rejoinable-game into master
Reviewed-on: #11
2024-05-19 12:41:02 +00:00
2bed5409ef flutter pub upgrade 2024-05-15 21:15:20 +02:00
544e0b22c5 Make games rejoinable
1. Disconnect websocket connection before connecting
2. store playerInfo when hosting a game to reuse it when rejoining
2024-05-15 19:44:02 +02:00
618102dd67 Merge pull request 'Fix colors and make passphrase submittable via Enter' (#10) from fix-dialog-colors into master
Reviewed-on: #10
2024-05-11 18:01:25 +00:00
32caf86f7f Fix colors and make passphrase submittable via Enter
With this change, the lobby selector gets its dark background back.
Also, now the passphrase can be submitted by pressing Enter and not only
by clicking the 'check' icon.
2024-05-11 19:58:29 +02:00
67a4be17cd bump version 2024-05-09 22:48:15 +02:00
fb42a05f72 flutter upgrade & flutter pub upgrade 2024-05-09 22:45:20 +02:00
ebab1a4c46 upgrade dependencies 2024-03-11 02:05:32 +01:00
320dd247ff Bump version 2024-02-05 10:51:56 +01:00
fd51e582af Merge pull request 'Fix snackbar in host/join dialog' (#9) from fix-dialog-snackbar into master
Reviewed-on: #9
2024-02-05 10:48:30 +01:00
9f64959498 Revert erroneous refactor 2024-02-05 10:39:01 +01:00
ba478fedca Fix snackbar in host/join dialog 2024-02-05 10:35:28 +01:00
93a84d442e bump version 2024-02-01 11:43:49 +01:00
33e79a90c9 Merge pull request 'Fix tapping on opponent's piece' (#8) from fix-tap-on-drag into master
Reviewed-on: #8
2024-02-01 11:42:18 +01:00
200393ac76 Fix tapping on opponent's piece
With the previous change of starting a tap on a drag,
we removed the GestureDetector from all squares with any pieces.

This included the opponent's pieces. Tapping and taking an opponent's
piece was not possible anymore.

This was a bug, since we still want to detect a tap and take an opponent's piece.
2024-02-01 11:37:00 +01:00
e22dc213ac Bump version 2024-02-01 11:22:25 +01:00
ecca9f07c3 Merge pull request 'Start a tap with drag' (#7) from drag-starts-tap into master
Reviewed-on: #7
2024-02-01 11:19:57 +01:00
8fd01eed1e Remove conditional_wrap 2024-02-01 11:18:58 +01:00
6882505174 Start a tap with drag
Now, only the empty squares contain a GestureDetector.
The GestureDetector is not necessary anymore in squares with pieces
because the tap is started when the drag is started.
2024-02-01 11:11:11 +01:00
c9381aaa4c Merge pull request 'Introduce checkmate screen' (#6) from checkmate-screen into master
Reviewed-on: #6
2024-01-18 17:03:30 +01:00
13bcfb1131 Introduce checkmate screen
Show checkmate screen, once the server sends the 'gameEnded' message.

Additionally, show an icon that lets users copy the passphrase to the
clipboard.
2024-01-18 16:59:33 +01:00
7d55a0e123 Merge pull request 'Implement moves by tapping the squares' (#5) from move-by-tap into master
Reviewed-on: #5
2024-01-17 20:59:31 +01:00
212a54612c Implement moves by tapping the squares
This adds an option to dragging-and-dropping which is slightly hard on
smaller screens.

Fix promotions when tapping and fix handling of subsequently tapping two pieces of your color

Cancel tap if a drag is started (tapped square will not stay red in case a drag is started)

Change url strategy back to the hashtag thing

Change version

Fix bug that would not allow a piece move if you tried to take an opponents piece.

Fix the coloring of the last move after an invalid move was played.

Upgrading deps
2024-01-17 20:58:13 +01:00
dfd9f09ee6 Update pubspec 2024-01-05 22:58:05 +01:00
cb8e98ef81 Fix building of web and linux app at the same time 2023-12-27 15:57:01 +01:00
2fe77063d9 Merge pull request 'Fix routing again' (#4) from fix-routing-again into master
Reviewed-on: #4
2023-12-27 15:49:04 +01:00
cdc0144e39 Fix routing again 2023-12-27 15:46:15 +01:00
289ec6db26 Fix turnColor handling 2023-12-25 18:08:21 +01:00
ba947ae5e4 Fix routing and move handling 2023-12-25 17:50:58 +01:00
17ac437f5b Pop promotion dialog with context.pop() 2023-12-25 02:07:03 +01:00
abf322572d Merge pull request 'Make passphrase entry a dialog instead of a page.' (#3) from store-lobby-id-and-player-id into master
Reviewed-on: #3
2023-12-23 16:47:07 +01:00
7a51e71767 Make passphrase entry a dialog instead of a page.
Additionally, we set some groundwork for storing the game data (lobby
id, player id, passphrase) in permanent storage in order to reconnect
with it later.
2023-12-23 16:44:23 +01:00
4a9047fd67 Merge pull request 'Handle board status message' (#2) from handle-status-reconnects into master
Reviewed-on: #2
2023-12-09 20:51:15 +01:00
8f4cd2266f 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.

Remove unused import
2023-12-09 20:48:36 +01:00
e441aaec1e pub ugrade 2023-11-27 00:20:02 +01:00
34 changed files with 1282 additions and 777 deletions

3
.gitignore vendored
View File

@ -42,3 +42,6 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
# Custom ignores
deploy-web.sh

1
devtools_options.yaml Normal file
View File

@ -0,0 +1 @@
extensions:

65
lib/api/game_info.dart Normal file
View File

@ -0,0 +1,65 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
class GameInfo {
final UuidValue? playerID;
final String? passphrase;
const GameInfo({
required this.playerID,
required this.passphrase,
});
factory GameInfo.empty() {
return const GameInfo(playerID: null, passphrase: null);
}
factory GameInfo.fromJson(Map<String, dynamic> json) {
final playerid = UuidValue.fromString(json['playerID']);
final passphrase = json['passphrase'];
return GameInfo(playerID: playerid, passphrase: passphrase);
}
Map<String, dynamic> toJson() {
String? pid;
if (playerID != null) {
pid = playerID.toString();
}
return {
'playerID': pid,
'passphrase': passphrase,
};
}
void store() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString(passphrase!, playerID.toString());
}
static Future<GameInfo?> get(String phrase) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
var playerID = prefs.getString(phrase);
if (playerID == null) return null;
return GameInfo(
playerID: UuidValue.fromString(playerID), passphrase: phrase);
}
}
class WebsocketMessageIdentifyPlayer {
final String playerID;
final String? passphrase;
const WebsocketMessageIdentifyPlayer({
required this.playerID,
required this.passphrase,
});
Map<String, dynamic> toJson() =>
{'playerID': playerID, 'passphrase': passphrase};
}

View File

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

View File

@ -1,9 +1,11 @@
import 'package:mchess/api/move.dart'; import 'package:mchess/api/move.dart';
enum MessageType { enum MessageType {
boardState,
move, move,
invalidMove, invalidMove,
colorDetermined; colorDetermined,
gameEnded;
String toJson() => name; String toJson() => name;
static MessageType fromJson(String json) => values.byName(json); static MessageType fromJson(String json) => values.byName(json);
@ -20,7 +22,8 @@ enum ApiColor {
class ApiWebsocketMessage { class ApiWebsocketMessage {
final MessageType type; final MessageType type;
final ApiMove? move; final ApiMove? move;
final ApiColor? color; final ApiColor? turnColor;
final ApiColor? playerColor;
final String? reason; final String? reason;
final String? position; final String? position;
final ApiCoordinate? squareInCheck; final ApiCoordinate? squareInCheck;
@ -28,7 +31,8 @@ class ApiWebsocketMessage {
ApiWebsocketMessage({ ApiWebsocketMessage({
required this.type, required this.type,
required this.move, required this.move,
required this.color, required this.turnColor,
required this.playerColor,
required this.reason, required this.reason,
required this.position, required this.position,
required this.squareInCheck, required this.squareInCheck,
@ -38,34 +42,56 @@ class ApiWebsocketMessage {
final type = MessageType.fromJson(json['messageType']); final type = MessageType.fromJson(json['messageType']);
ApiWebsocketMessage ret; ApiWebsocketMessage ret;
switch (type) { switch (type) {
case MessageType.boardState:
ret = ApiWebsocketMessage(
type: type,
move: ApiMove.fromJson(json['move']),
turnColor: ApiColor.fromJson(json['turnColor']),
reason: null,
position: json['position'],
squareInCheck: null,
playerColor: ApiColor.fromJson(json['playerColor']),
);
case MessageType.colorDetermined: case MessageType.colorDetermined:
ret = ApiWebsocketMessage( ret = ApiWebsocketMessage(
type: type, type: type,
move: null, move: null,
color: ApiColor.fromJson(json['color']), turnColor: null,
reason: null, reason: null,
position: null, position: null,
squareInCheck: null, squareInCheck: null,
playerColor: ApiColor.fromJson(json['playerColor']),
); );
break; break;
case MessageType.move: case MessageType.move:
ret = ApiWebsocketMessage( ret = ApiWebsocketMessage(
type: type, type: type,
move: ApiMove.fromJson(json['move']), move: ApiMove.fromJson(json['move']),
color: null, turnColor: null,
reason: null, reason: null,
position: json['position'], position: json['position'],
squareInCheck: json['squareInCheck'] squareInCheck: json['squareInCheck'],
); playerColor: null);
break; break;
case MessageType.invalidMove: case MessageType.invalidMove:
ret = ApiWebsocketMessage( ret = ApiWebsocketMessage(
type: type, type: type,
move: ApiMove.fromJson(json['move']), move: ApiMove.fromJson(json['move']),
color: null, turnColor: null,
reason: json['reason'], reason: json['reason'],
position: null, position: null,
squareInCheck: json['squareInCheck'], squareInCheck: json['squareInCheck'],
playerColor: null,
);
case MessageType.gameEnded:
ret = ApiWebsocketMessage(
type: type,
move: null,
turnColor: null,
reason: json['reason'],
position: null,
squareInCheck: null,
playerColor: null,
); );
} }
return ret; return ret;
@ -74,6 +100,6 @@ class ApiWebsocketMessage {
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'messageType': type, 'messageType': type,
'move': move, 'move': move,
'color': color, 'color': turnColor,
}; };
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/promotion_bloc.dart'; import 'package:mchess/chess_bloc/promotion_bloc.dart';
import 'package:mchess/chess_bloc/tap_bloc.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart'; import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/utils/chess_router.dart'; import 'package:mchess/utils/chess_router.dart';
@ -16,18 +17,21 @@ class ChessApp extends StatelessWidget {
create: (_) => ConnectionCubit.getInstance(), create: (_) => ConnectionCubit.getInstance(),
), ),
BlocProvider( BlocProvider(
create: (context) => ChessBloc.getInstance(), create: (_) => ChessBloc.getInstance(),
), ),
BlocProvider( BlocProvider(
create: (context) => PromotionBloc.getInstance(), create: (_) => PromotionBloc.getInstance(),
) ),
BlocProvider(
create: (_) => TapBloc.getInstance(),
),
], ],
child: MaterialApp.router( child: MaterialApp.router(
theme: ThemeData.dark( theme: ThemeData.dark(
useMaterial3: true, useMaterial3: true,
), ),
routerConfig: ChessAppRouter.getInstance().router, routerConfig: ChessAppRouter.getInstance().router,
title: 'mChess', title: 'mChess 1.0.8',
), ),
); );
} }

View File

@ -13,7 +13,8 @@ class ChessBoard extends StatelessWidget {
const ChessBoard._({required this.bState, required this.squares}); const ChessBoard._({required this.bState, required this.squares});
factory ChessBoard({required ChessBoardState boardState}) { factory ChessBoard(
{required ChessBoardState boardState, ChessCoordinate? tappedSquare}) {
List<ChessSquare> squares = List.empty(growable: true); List<ChessSquare> squares = List.empty(growable: true);
for (int i = 0; i < 64; i++) { for (int i = 0; i < 64; i++) {
var column = (i % 8) + 1; var column = (i % 8) + 1;
@ -24,16 +25,16 @@ class ChessBoard extends StatelessWidget {
bool squareWasPartOfLastMove = false; bool squareWasPartOfLastMove = false;
if ((boardState.lastMove.to == ChessCoordinate(column, row) || if ((boardState.lastMove.to == ChessCoordinate(column, row) ||
boardState.lastMove.from == ChessCoordinate(column, row)) && boardState.lastMove.from == ChessCoordinate(column, row)) &&
boardState.positionAckdByServer) { boardState.colorLastMove) {
squareWasPartOfLastMove = true; squareWasPartOfLastMove = true;
} }
squares.add( squares.add(
ChessSquare( ChessSquare(
ChessCoordinate(column, row), ChessCoordinate(column, row),
piece, piece,
squareWasPartOfLastMove, squareWasPartOfLastMove,
), tappedSquare == ChessCoordinate(column, row)),
); );
} }

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mchess/chess/chess_square_outer_dragtarget.dart'; import 'package:mchess/chess/chess_square_outer_dragtarget.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/tap_bloc.dart';
import '../utils/chess_utils.dart'; import '../utils/chess_utils.dart';
class ChessSquare extends StatefulWidget { class ChessSquare extends StatefulWidget {
@ -18,13 +18,18 @@ class ChessSquare extends StatefulWidget {
required this.color, required this.color,
}); });
factory ChessSquare( factory ChessSquare(ChessCoordinate coord, ChessPiece? piece,
ChessCoordinate coord, ChessPiece? piece, bool wasPartOfLastMove) { bool wasPartOfLastMove, bool wasTapped) {
Color lightSquaresColor = Color lightSquaresColor =
wasPartOfLastMove ? Colors.green.shade200 : Colors.brown.shade50; wasPartOfLastMove ? Colors.green.shade200 : Colors.brown.shade50;
Color darkSquaresColor = Color darkSquaresColor =
wasPartOfLastMove ? Colors.green.shade300 : Colors.brown.shade400; wasPartOfLastMove ? Colors.green.shade300 : Colors.brown.shade400;
if (wasTapped) {
lightSquaresColor = Colors.red.shade200;
darkSquaresColor = Colors.red.shade300;
}
Color squareColor; Color squareColor;
if (coord.row % 2 == 0) { if (coord.row % 2 == 0) {
@ -53,30 +58,26 @@ class ChessSquare extends StatefulWidget {
} }
class _ChessSquareState extends State<ChessSquare> { class _ChessSquareState extends State<ChessSquare> {
late Color squareColor;
@override
void initState() {
squareColor = widget.color;
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocListener<ChessBloc, ChessBoardState>( var dragTarget = Container(
listenWhen: (previous, current) { color: widget.color,
return true; child: ChessSquareOuterDragTarget(
}, coordinate: widget.coordinate,
listener: (context, state) { containedPiece: widget.containedPiece ?? const ChessPiece.none()),
setState(() {
squareColor = Colors.red;
});},
child: Container(
color: widget.color,
child: ChessSquareOuterDragTarget(
coordinate: widget.coordinate,
containedPiece: widget.containedPiece ?? const ChessPiece.none()),
),
); );
if (widget.containedPiece == null ||
widget.containedPiece!.color != ChessBloc.getMyColor()) {
return GestureDetector(
child: dragTarget,
onTap: () {
TapBloc().add(SquareTappedEvent(
tapped: widget.coordinate, pieceOnSquare: widget.containedPiece));
},
);
} else {
return dragTarget;
}
} }
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mchess/chess/chess_square.dart'; import 'package:mchess/chess/chess_square.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/tap_bloc.dart';
import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
class ChessSquareInnerDraggable extends StatelessWidget { class ChessSquareInnerDraggable extends StatelessWidget {
@ -48,7 +49,10 @@ class ChessSquareInnerDraggable extends StatelessWidget {
dragAnchorStrategy: pointerDragAnchorStrategy, dragAnchorStrategy: pointerDragAnchorStrategy,
child: containedPiece ?? Container(), child: containedPiece ?? Container(),
onDragCompleted: () {}, onDragCompleted: () {},
onDragStarted: () {}, onDragStarted: () {
TapBloc().add(SquareTappedEvent(
tapped: coordinate, pieceOnSquare: containedPiece));
},
), ),
); );
} }

View File

@ -3,6 +3,7 @@ import 'package:mchess/chess/chess_square_inner_draggable.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart'; import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/chess_bloc/promotion_bloc.dart'; import 'package:mchess/chess_bloc/promotion_bloc.dart';
import 'package:mchess/chess_bloc/tap_bloc.dart';
import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
class ChessSquareOuterDragTarget extends StatelessWidget { class ChessSquareOuterDragTarget extends StatelessWidget {
@ -10,33 +11,36 @@ class ChessSquareOuterDragTarget extends StatelessWidget {
final ChessPiece containedPiece; final ChessPiece containedPiece;
const ChessSquareOuterDragTarget( const ChessSquareOuterDragTarget(
{super.key, {super.key, required this.coordinate, required this.containedPiece});
required this.coordinate,
required this.containedPiece});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DragTarget<PieceDragged>( return DragTarget<PieceDragged>(
onWillAccept: (move) { onWillAcceptWithDetails: (details) {
if (move?.fromSquare == coordinate) { if (details.data.fromSquare == coordinate) {
return false; return false;
} }
return true; return true;
}, },
onAccept: (pieceDragged) { onAcceptWithDetails: (details) {
// Replace the dummy value with the actual target of the move. // Replace the dummy value with the actual target of the move.
pieceDragged.toSquare = coordinate; details.data.toSquare = coordinate;
if (isPromotionMove(pieceDragged)) { TapBloc().add(CancelTapEvent());
if (isPromotionMove(
details.data.movedPiece!.pieceClass,
ChessBloc.getMyColor(),
details.data.toSquare,
)) {
var move = ChessMove( var move = ChessMove(
from: pieceDragged.fromSquare, to: pieceDragged.toSquare); from: details.data.fromSquare, to: details.data.toSquare);
PromotionBloc.getInstance().add(PawnMovedToPromotionField( PromotionBloc().add(PawnMovedToPromotionField(
move: move, colorMoved: ChessBloc.myColor!)); move: move, colorMoved: ChessBloc.myColor));
} else if (coordinate != pieceDragged.fromSquare) { } else if (coordinate != details.data.fromSquare) {
ChessBloc.getInstance().add(OwnPieceMoved( ChessBloc().add(OwnPieceMoved(
startSquare: pieceDragged.fromSquare, startSquare: details.data.fromSquare,
endSquare: pieceDragged.toSquare, endSquare: details.data.toSquare));
piece: pieceDragged.movedPiece!));
} }
}, },
builder: (context, candidateData, rejectedData) { builder: (context, candidateData, rejectedData) {
@ -47,28 +51,4 @@ class ChessSquareOuterDragTarget extends StatelessWidget {
}, },
); );
} }
bool isPromotionMove(PieceDragged move) {
bool isPromotion = false;
if (move.movedPiece!.pieceClass != ChessPieceClass.pawn) {
return isPromotion;
}
switch (ChessBloc.myColor) {
case ChessColor.black:
if (move.toSquare.row == 1) {
isPromotion = true;
}
break;
case ChessColor.white:
if (move.toSquare.row == 8) {
isPromotion = true;
}
break;
case null:
break;
}
return isPromotion;
}
} }

View File

@ -11,16 +11,16 @@ import 'package:mchess/utils/chess_utils.dart';
class ChessBloc extends Bloc<ChessEvent, ChessBoardState> { class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
static final ChessBloc _instance = ChessBloc._internal(); static final ChessBloc _instance = ChessBloc._internal();
static ChessColor turnColor = ChessColor.white; static ChessColor turnColor = ChessColor.white;
static ChessColor? myColor = ChessColor.white; static ChessColor myColor = ChessColor.white;
static ChessColor? getSidesColor() { static ChessColor getMyColor() {
return myColor; return myColor;
} }
ChessBloc._internal() : super(ChessBoardState.init()) { ChessBloc._internal() : super(ChessBoardState.init()) {
on<InitBoard>(initBoard); on<InitBoard>(initBoard);
on<ColorDetermined>(flipBoard); on<ColorDetermined>(flipBoard);
on<ReceivedMove>(moveAndPositionHandler); on<ReceivedBoardState>(moveAndPositionHandler);
on<OwnPieceMoved>(ownMoveHandler); on<OwnPieceMoved>(ownMoveHandler);
on<OwnPromotionPlayed>(ownPromotionHandler); on<OwnPromotionPlayed>(ownPromotionHandler);
on<InvalidMovePlayed>(invalidMoveHandler); on<InvalidMovePlayed>(invalidMoveHandler);
@ -51,10 +51,12 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
void flipBoard(ColorDetermined event, Emitter<ChessBoardState> emit) { void flipBoard(ColorDetermined event, Emitter<ChessBoardState> emit) {
log("My Color is $myColor"); log("My Color is $myColor");
myColor = event.myColor;
myColor = event.playerColor;
emit( emit(
ChessBoardState( ChessBoardState(
event.myColor, event.playerColor,
state.newTurnColor, state.newTurnColor,
state.position, state.position,
ChessMove.none(), ChessMove.none(),
@ -65,21 +67,24 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
} }
void moveAndPositionHandler( void moveAndPositionHandler(
ReceivedMove event, ReceivedBoardState event, Emitter<ChessBoardState> emit) {
Emitter<ChessBoardState> emit, ChessMove? move;
) { if (event.startSquare != ChessCoordinate.none() &&
ChessPositionManager.getInstance() event.endSquare != ChessCoordinate.none()) {
.recordMove(event.startSquare, event.endSquare, event.position); move = ChessMove(from: event.startSquare!, to: event.endSquare!);
turnColor = state.newTurnColor == ChessColor.white ChessPositionManager.getInstance()
? ChessColor.black .recordMove(event.startSquare, event.endSquare, event.position);
: ChessColor.white; }
myColor = event.playerColor;
turnColor = event.turnColor;
emit( emit(
ChessBoardState( ChessBoardState(
state.bottomColor, myColor,
turnColor, event.turnColor,
event.position, event.position,
ChessMove(from: event.startSquare, to: event.endSquare), move,
true, true,
event.squareInCheck, event.squareInCheck,
), ),
@ -93,10 +98,11 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
var apiMessage = ApiWebsocketMessage( var apiMessage = ApiWebsocketMessage(
type: MessageType.move, type: MessageType.move,
move: apiMove, move: apiMove,
color: null, turnColor: null,
reason: null, reason: null,
position: null, position: null,
squareInCheck: null, squareInCheck: null,
playerColor: null,
); );
ServerConnection.getInstance().send(jsonEncode(apiMessage)); ServerConnection.getInstance().send(jsonEncode(apiMessage));
@ -108,8 +114,14 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
tempPosition[move.from] = const ChessPiece.none(); tempPosition[move.from] = const ChessPiece.none();
emit( emit(
ChessBoardState(state.bottomColor, turnColor, tempPosition, move, false, ChessBoardState(
ChessCoordinate.none()), state.bottomColor,
turnColor,
tempPosition,
ChessPositionManager.getInstance().lastMove,
true,
ChessCoordinate.none(),
),
); );
} }
@ -117,15 +129,16 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
OwnPromotionPlayed event, Emitter<ChessBoardState> emit) { OwnPromotionPlayed event, Emitter<ChessBoardState> emit) {
var apiMove = event.move.toApiMove(); var apiMove = event.move.toApiMove();
var shorNameForPiece = chessPiecesShortName[ var shorNameForPiece = chessPiecesShortName[
ChessPieceAssetKey(pieceClass: event.pieceClass, color: myColor!)]!; ChessPieceAssetKey(pieceClass: event.pieceClass, color: myColor)]!;
apiMove.promotionToPiece = shorNameForPiece; apiMove.promotionToPiece = shorNameForPiece;
var message = ApiWebsocketMessage( var message = ApiWebsocketMessage(
type: MessageType.move, type: MessageType.move,
move: apiMove, move: apiMove,
color: null, turnColor: null,
reason: null, reason: null,
position: null, position: null,
squareInCheck: null, squareInCheck: null,
playerColor: null,
); );
log(jsonEncode(message)); log(jsonEncode(message));
ServerConnection.getInstance().send(jsonEncode(message)); ServerConnection.getInstance().send(jsonEncode(message));
@ -133,13 +146,14 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
void invalidMoveHandler( void invalidMoveHandler(
InvalidMovePlayed event, Emitter<ChessBoardState> emit) { InvalidMovePlayed event, Emitter<ChessBoardState> emit) {
var move = ChessPositionManager.getInstance().lastMove;
emit( emit(
ChessBoardState( ChessBoardState(
state.bottomColor, state.bottomColor,
turnColor, turnColor,
ChessPositionManager.getInstance().currentPosition, ChessPositionManager.getInstance().currentPosition,
ChessMove.none(), move,
false, true,
event.squareInCheck, event.squareInCheck,
), ),
); );
@ -151,7 +165,7 @@ class ChessBoardState {
final ChessColor newTurnColor; final ChessColor newTurnColor;
final ChessPosition position; final ChessPosition position;
final ChessMove lastMove; final ChessMove lastMove;
final bool positionAckdByServer; final bool colorLastMove;
final ChessCoordinate squareInCheck; final ChessCoordinate squareInCheck;
ChessBoardState._( ChessBoardState._(
@ -159,7 +173,7 @@ class ChessBoardState {
this.newTurnColor, this.newTurnColor,
this.position, this.position,
this.lastMove, this.lastMove,
this.positionAckdByServer, this.colorLastMove,
this.squareInCheck, this.squareInCheck,
); );
@ -167,12 +181,12 @@ class ChessBoardState {
ChessColor bottomColor, ChessColor bottomColor,
ChessColor turnColor, ChessColor turnColor,
ChessPosition position, ChessPosition position,
ChessMove lastMove, ChessMove? lastMove,
bool positionAckd, bool positionAckd,
ChessCoordinate squareInCheck, ChessCoordinate squareInCheck,
) { ) {
return ChessBoardState._(bottomColor, turnColor, position, lastMove, return ChessBoardState._(bottomColor, turnColor, position,
positionAckd, squareInCheck); lastMove ?? ChessMove.none(), positionAckd, squareInCheck);
} }
factory ChessBoardState.init() { factory ChessBoardState.init() {

View File

@ -3,29 +3,29 @@ import 'package:mchess/utils/chess_utils.dart';
abstract class ChessEvent {} abstract class ChessEvent {}
class ReceivedMove extends ChessEvent { class ReceivedBoardState extends ChessEvent {
final ChessCoordinate startSquare; final ChessCoordinate? startSquare;
final ChessCoordinate endSquare; final ChessCoordinate? endSquare;
final ChessPosition position; final ChessPosition position;
final ChessCoordinate squareInCheck; final ChessCoordinate squareInCheck;
final ChessColor turnColor;
final ChessColor playerColor;
ReceivedMove({ ReceivedBoardState({
required this.startSquare, required this.startSquare,
required this.endSquare, required this.endSquare,
required this.position, required this.position,
required this.squareInCheck, required this.squareInCheck,
required this.turnColor,
required this.playerColor,
}); });
} }
class OwnPieceMoved extends ChessEvent { class OwnPieceMoved extends ChessEvent {
final ChessCoordinate startSquare; final ChessCoordinate startSquare;
final ChessCoordinate endSquare; final ChessCoordinate endSquare;
final ChessPiece piece;
OwnPieceMoved( OwnPieceMoved({required this.startSquare, required this.endSquare});
{required this.startSquare,
required this.endSquare,
required this.piece});
} }
class OwnPromotionPlayed extends ChessEvent { class OwnPromotionPlayed extends ChessEvent {
@ -40,9 +40,9 @@ class InitBoard extends ChessEvent {
} }
class ColorDetermined extends ChessEvent { class ColorDetermined extends ChessEvent {
final ChessColor myColor; final ChessColor playerColor;
ColorDetermined({required this.myColor}); ColorDetermined({required this.playerColor});
} }
class InvalidMovePlayed extends ChessEvent { class InvalidMovePlayed extends ChessEvent {

View File

@ -71,10 +71,16 @@ class ChessPositionManager {
log(logString); log(logString);
} }
void recordMove(ChessCoordinate from, ChessCoordinate to, ChessPosition pos) { void recordMove(
ChessCoordinate? from, ChessCoordinate? to, ChessPosition pos) {
position = pos; position = pos;
history.add(ChessMove(from: from, to: to)); history.add(
ChessMove(
from: from ?? ChessCoordinate.none(),
to: to ?? ChessCoordinate.none(),
),
);
logPosition(position); logPosition(position);
logHistory(history); logHistory(history);
@ -89,6 +95,15 @@ class ChessPositionManager {
return _instance; return _instance;
} }
static ChessPosition _getDebugPostion() {
ChessPosition pos = {};
pos[ChessCoordinate(7, 7)] =
ChessPiece(ChessPieceClass.pawn, ChessColor.white);
return pos;
}
static ChessPosition _getStartingPosition() { static ChessPosition _getStartingPosition() {
ChessPosition pos = {}; ChessPosition pos = {};

View File

@ -0,0 +1,109 @@
import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/chess_bloc/promotion_bloc.dart';
import 'package:mchess/utils/chess_utils.dart';
class TapBloc extends Bloc<TapEvent, TapState> {
static final TapBloc _instance = TapBloc._internal();
static TapBloc getInstance() {
return _instance;
}
factory TapBloc() {
return _instance;
}
TapBloc._internal() : super(TapState.init()) {
on<SquareTappedEvent>(handleTap);
on<CancelTapEvent>(cancelTap);
}
void handleTap(SquareTappedEvent event, Emitter<TapState> emit) {
ChessCoordinate? firstTappedSquare, secondTappedSquare;
ChessPiece? piece;
if (ChessBloc.myColor != ChessBloc.turnColor) return;
if (state.firstSquareTapped == null) {
//first tap
if (event.pieceOnSquare == null) return;
if (event.pieceOnSquare != null &&
ChessBloc.myColor != event.pieceOnSquare!.color) return;
firstTappedSquare = event.tapped;
piece = event.pieceOnSquare;
} else {
//second tap
if (event.pieceOnSquare?.color == ChessBloc.myColor) {
emit(TapState(
firstSquareTapped: event.tapped,
pieceOnFirstTappedSquare: event.pieceOnSquare,
secondSquareTapped: null));
return;
}
secondTappedSquare = event.tapped;
}
if (state.firstSquareTapped != null &&
state.firstSquareTapped != event.tapped) {
if (isPromotionMove(
state.pieceOnFirstTappedSquare!.pieceClass,
ChessBloc.myColor,
event.tapped,
)) {
PromotionBloc().add(PawnMovedToPromotionField(
move: ChessMove(from: state.firstSquareTapped!, to: event.tapped),
colorMoved: ChessBloc.myColor));
emit(TapState.init());
return;
} else {
ChessBloc().add(OwnPieceMoved(
startSquare: state.firstSquareTapped!, endSquare: event.tapped));
emit(TapState.init());
return;
}
}
log("handleTap() in TapBloc is called");
emit(TapState(
firstSquareTapped: firstTappedSquare,
pieceOnFirstTappedSquare: piece,
secondSquareTapped: secondTappedSquare));
}
void cancelTap(CancelTapEvent event, Emitter<TapState> emit) {
emit(TapState.init());
}
}
abstract class TapEvent {}
class SquareTappedEvent extends TapEvent {
ChessCoordinate tapped;
ChessPiece? pieceOnSquare;
SquareTappedEvent({required this.tapped, required this.pieceOnSquare});
}
class CancelTapEvent extends TapEvent {}
class TapState {
ChessCoordinate? firstSquareTapped;
ChessPiece? pieceOnFirstTappedSquare;
ChessCoordinate? secondSquareTapped;
TapState(
{required this.firstSquareTapped,
required this.pieceOnFirstTappedSquare,
required this.secondSquareTapped});
factory TapState.init() {
return TapState(
firstSquareTapped: null,
pieceOnFirstTappedSquare: null,
secondSquareTapped: null);
}
}

View File

@ -1,20 +1,21 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mchess/api/move.dart'; import 'package:mchess/api/move.dart';
import 'package:mchess/api/websocket_message.dart'; import 'package:mchess/api/websocket_message.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart'; import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart'; import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/api/register.dart'; import 'package:mchess/api/game_info.dart';
import 'package:mchess/chess_bloc/chess_position.dart'; import 'package:mchess/chess_bloc/chess_position.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart'; import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/utils/chess_router.dart';
import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
import 'package:mchess/utils/config.dart' as config;
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart';
class ServerConnection { class ServerConnection {
late WebSocketChannel channel; WebSocketChannel? channel;
late bool wasConnected = false;
late int counter = 0;
Stream broadcast = const Stream.empty(); Stream broadcast = const Stream.empty();
static final ServerConnection _instance = ServerConnection._internal(); static final ServerConnection _instance = ServerConnection._internal();
@ -32,71 +33,87 @@ class ServerConnection {
} }
void send(String message) { void send(String message) {
channel.sink.add(message); if (channel == null) {
counter++; log("Sending on channel without initializing");
return;
}
channel!.sink.add(message);
} }
void connect(String playerID, lobbyID, String? passphrase) { Future? connect(String playerID, String? passphrase) {
if (kDebugMode) { if (channel != null) return null;
channel =
WebSocketChannel.connect(Uri.parse('ws://localhost:8080/api/ws'));
} else {
channel = WebSocketChannel.connect(
Uri.parse('wss://chess.sw-gross.de:9999/api/ws'));
}
send(
jsonEncode(
WebsocketMessageIdentifyPlayer(
playerID: (playerID),
lobbyID: (lobbyID),
passphrase: (passphrase),
),
),
);
log(channel.closeCode.toString()); channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL()));
broadcast = channel.stream.asBroadcastStream();
broadcast.listen(handleIncomingData); channel!.ready.then((val) {
send(
jsonEncode(
WebsocketMessageIdentifyPlayer(
playerID: (playerID),
passphrase: (passphrase),
),
),
);
log(channel!.closeCode.toString());
broadcast = channel!.stream.asBroadcastStream();
broadcast.listen(handleIncomingData);
});
return channel!.ready;
}
Future disconnectExistingConnection() async {
if (channel == null) return;
await channel!.sink.close();
channel = null;
broadcast = const Stream.empty();
} }
void handleIncomingData(dynamic data) { void handleIncomingData(dynamic data) {
log("Data received:"); log('${DateTime.now()}: Data received:');
log(data); log(data);
var apiMessage = ApiWebsocketMessage.fromJson(jsonDecode(data)); var apiMessage = ApiWebsocketMessage.fromJson(jsonDecode(data));
switch (apiMessage.type) { switch (apiMessage.type) {
case MessageType.boardState:
handleBoardStateMessage(apiMessage);
break;
case MessageType.colorDetermined: case MessageType.colorDetermined:
handleIncomingColorDeterminedMessage(apiMessage); handleIncomingColorDeterminedMessage(apiMessage);
break; break;
case MessageType.move: case MessageType.move:
handleIncomingMoveMessage(apiMessage); log('ERROR: move message received');
break; break;
case MessageType.invalidMove: case MessageType.invalidMove:
handleInvalidMoveMessage(apiMessage); handleInvalidMoveMessage(apiMessage);
case MessageType.gameEnded:
handleGameEndedMessage(apiMessage);
} }
} }
void handleIncomingColorDeterminedMessage(ApiWebsocketMessage apiMessage) { void handleBoardStateMessage(ApiWebsocketMessage apiMessage) {
ConnectionCubit.getInstance().opponentConnected(); ChessMove? move;
ChessBloc.getInstance().add(InitBoard()); if (apiMessage.move != null) {
ChessBloc.getInstance().add( move = ChessMove.fromApiMove(apiMessage.move!);
ColorDetermined(myColor: ChessColor.fromApiColor(apiMessage.color!))); }
}
void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) {
var move = ChessMove.fromApiMove(apiMessage.move!);
if (apiMessage.position != null) { if (apiMessage.position != null) {
ChessBloc.getInstance().add( ChessBloc.getInstance().add(
ReceivedMove( ReceivedBoardState(
startSquare: move.from, startSquare: move?.from,
endSquare: move.to, endSquare: move?.to,
position: ChessPositionManager.getInstance() position: ChessPositionManager.getInstance()
.fromPGNString(apiMessage.position!), .fromPGNString(apiMessage.position!),
squareInCheck: squareInCheck: ChessCoordinate.fromApiCoordinate(
ChessCoordinate.fromApiCoordinate(apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)), apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
turnColor: ChessColor.fromApiColor(apiMessage.turnColor!),
playerColor: ChessColor.fromApiColor(apiMessage.playerColor!),
), ),
); );
} else { } else {
@ -104,14 +121,46 @@ class ServerConnection {
} }
} }
void handleIncomingColorDeterminedMessage(ApiWebsocketMessage apiMessage) {
ConnectionCubit.getInstance().opponentConnected();
ChessBloc.getInstance().add(InitBoard());
ChessBloc.getInstance().add(ColorDetermined(
playerColor: ChessColor.fromApiColor(apiMessage.playerColor!)));
}
void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) { void handleInvalidMoveMessage(ApiWebsocketMessage apiMessage) {
log("invalid move message received, with move: ${apiMessage.move.toString()}"); log("invalid move message received, with move: ${apiMessage.move.toString()}");
ChessBloc.getInstance().add( ChessBloc.getInstance().add(
InvalidMovePlayed( InvalidMovePlayed(
move: ChessMove.fromApiMove(apiMessage.move!), move: ChessMove.fromApiMove(apiMessage.move!),
squareInCheck: squareInCheck: ChessCoordinate.fromApiCoordinate(
ChessCoordinate.fromApiCoordinate(apiMessage.squareInCheck!), apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
), ),
); );
} }
void handleGameEndedMessage(ApiWebsocketMessage apiMessage) {
showDialog(
context: navigatorKey.currentContext!,
builder: (context) {
String msg = '';
if (apiMessage.reason == 'whiteIsCheckmated') {
msg = 'Black won! White is checkmated';
} else if (apiMessage.reason == 'blackIsCheckmated') {
msg = 'White won! Black is checkmated';
}
return AlertDialog(
title: const Text('Game ended'),
content: Text(msg),
actions: <Widget>[
TextButton(
child: const Text('Home'),
onPressed: () {
navigatorKey.currentContext!.goNamed('lobbySelector');
},
),
]);
},
);
}
} }

View File

@ -15,21 +15,64 @@ class ConnectionCubit extends Cubit<ConnectionCubitState> {
return _instance; return _instance;
} }
void connect(String playerID, lobbyID, String? passphrase) { void connect(String playerID, String? passphrase) {
ServerConnection.getInstance().connect(playerID, lobbyID, passphrase); var connectedFuture =
ServerConnection.getInstance().connect(playerID, passphrase);
connectedFuture?.then((val) {
emit(ConnectionCubitState(
iAmConnected: true,
connectedToPhrase: passphrase,
opponentConnected: false));
});
}
void connectIfNotConnected(String playerID, String? passphrase) {
if (state.iAmConnected && state.connectedToPhrase == passphrase) {
return;
}
if (state.iAmConnected && state.connectedToPhrase != passphrase) {
disonnect().then((val) {
connect(playerID, passphrase);
});
}
connect(playerID, passphrase);
}
Future disonnect() async {
var disconnectFuture =
ServerConnection.getInstance().disconnectExistingConnection();
disconnectFuture.then(
(val) {
emit(ConnectionCubitState.init());
},
);
return disconnectFuture;
} }
void opponentConnected() { void opponentConnected() {
emit(ConnectionCubitState(true)); emit(ConnectionCubitState(
iAmConnected: state.iAmConnected,
connectedToPhrase: state.connectedToPhrase,
opponentConnected: true));
} }
} }
class ConnectionCubitState { class ConnectionCubitState {
final bool iAmConnected;
final String? connectedToPhrase;
final bool opponentConnected; final bool opponentConnected;
ConnectionCubitState(this.opponentConnected); ConnectionCubitState(
{required this.iAmConnected,
required this.connectedToPhrase,
required this.opponentConnected});
factory ConnectionCubitState.init() { factory ConnectionCubitState.init() {
return ConnectionCubitState(false); return ConnectionCubitState(
iAmConnected: false, connectedToPhrase: null, opponentConnected: false);
} }
} }

View File

@ -1,6 +1,8 @@
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

@ -5,58 +5,64 @@ import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess/chess_board.dart'; import 'package:mchess/chess/chess_board.dart';
import 'package:mchess/chess_bloc/promotion_bloc.dart'; import 'package:mchess/chess_bloc/promotion_bloc.dart';
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:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class ChessGame extends StatefulWidget { class ChessGame extends StatefulWidget {
final UuidValue playerID; const ChessGame({super.key});
final UuidValue lobbyID;
final String? passphrase;
const ChessGame(
{required this.playerID,
required this.lobbyID,
required this.passphrase,
super.key});
@override @override
State<ChessGame> createState() => _ChessGameState(); State<ChessGame> createState() => _ChessGameState();
} }
class _ChessGameState extends State<ChessGame> { class _ChessGameState extends State<ChessGame> {
@override ChessCoordinate? tappedSquare;
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
FloatingActionButton? fltnBtn;
if (UniversalPlatform.isLinux ||
UniversalPlatform.isMacOS ||
UniversalPlatform.isWindows) {
fltnBtn = FloatingActionButton(
child: const Icon(Icons.cancel),
onPressed: () {
context.pop();
},
);
}
return Scaffold( return Scaffold(
floatingActionButton: fltnBtn,
body: Center( body: Center(
child: Container( child: Container(
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
child: BlocListener<PromotionBloc, PromotionState>( child: BlocListener<TapBloc, TapState>(
listener: (context, state) { listener: (context, state) {
if (state.showPromotionDialog) { setState(() {
promotionDialogBuilder(context, state.colorMoved); tappedSquare = state.firstSquareTapped;
} });
}, },
child: BlocBuilder<ChessBloc, ChessBoardState>( child: BlocListener<PromotionBloc, PromotionState>(
builder: (context, state) { listener: (listenerContext, state) {
return ChessBoard( if (state.showPromotionDialog) {
boardState: state, promotionDialogBuilder(listenerContext, state.colorMoved);
); }
}, },
child: BlocBuilder<ChessBloc, ChessBoardState>(
builder: (context, state) {
return ChessBoard(
boardState: state,
tappedSquare: tappedSquare,
);
},
),
), ),
), ),
), ),
), ),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.push('/');
},
child: const Icon(Icons.cancel),
),
); );
} }
@ -83,4 +89,18 @@ class ChessGameArguments {
required this.playerID, required this.playerID,
required this.passphrase, 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

@ -0,0 +1,161 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart';
import 'package:mchess/api/game_info.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';
import 'package:mchess/utils/config.dart' as config;
import 'package:mchess/utils/passphrase.dart';
import 'package:universal_platform/universal_platform.dart';
class CreateGameWidget extends StatefulWidget {
const CreateGameWidget({super.key});
@override
State<CreateGameWidget> createState() => _CreateGameWidgetState();
}
class _CreateGameWidgetState extends State<CreateGameWidget> {
late Future<GameInfo?> registerResponse;
late Future disconnectFuture;
late ChessGameArguments chessGameArgs;
@override
void initState() {
super.initState();
disconnectFuture = ConnectionCubit().disonnect();
disconnectFuture.then((val) {
registerResponse = createPrivateGame();
registerResponse.then((val) {
ConnectionCubit().connectIfNotConnected(
val!.playerID.toString(),
val.passphrase,
);
});
});
}
@override
Widget build(BuildContext context) {
FloatingActionButton? fltnBtn;
if (UniversalPlatform.isLinux ||
UniversalPlatform.isMacOS ||
UniversalPlatform.isWindows) {
fltnBtn = FloatingActionButton(
child: const Icon(Icons.cancel),
onPressed: () {
context.pop();
},
);
}
return Scaffold(
floatingActionButton: fltnBtn,
body: Center(
child: FutureBuilder(
future: disconnectFuture,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Container();
} else {
return FutureBuilder<GameInfo?>(
future: registerResponse,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
);
} else {
var passphrase =
snapshot.data?.passphrase ?? "no passphrase";
return BlocListener<ConnectionCubit,
ConnectionCubitState>(
listener: (context, state) {
// We wait for our opponent to connect
if (state.opponentConnected) {
//TODO: is goNamed the correct way to navigate?
context.goNamed('game', pathParameters: {
'phrase': passphrase.toURL(),
});
}
},
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),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SelectableText(
passphrase,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(Icons.copy),
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: passphrase));
},
)
],
),
const SizedBox(height: 25),
const CircularProgressIndicator()
],
),
);
}
},
);
}
}),
),
);
}
Future<GameInfo?> createPrivateGame() async {
Response response;
try {
response = await http.get(Uri.parse(config.getCreateGameURL()),
headers: {"Accept": "application/json"});
} catch (e) {
log('Exception: ${e.toString()}');
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("mChess server is not responding. Try again or give up"),
);
Future.delayed(const Duration(seconds: 1), () {
if (!mounted) return null;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.goNamed('lobbySelector'); // We go back to the lobby selector
});
return null;
}
if (response.statusCode == 200) {
var info = GameInfo.fromJson(jsonDecode(response.body));
info.store();
return info;
}
return null;
}
}

View File

@ -1,140 +0,0 @@
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/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() {
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),
SelectableText(
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.push('/'); // We go back to the lobby selector
});
return null;
}
if (response.statusCode == 200) {
log(response.body);
return PlayerInfo.fromJson(jsonDecode(response.body));
}
return null;
}
}

View File

@ -1,118 +0,0 @@
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.push('/'); // We go back to lobby selector
});
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

@ -0,0 +1,146 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:mchess/api/game_info.dart';
import 'package:mchess/connection_cubit/connection_cubit.dart';
import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/utils/config.dart' as config;
import 'package:universal_platform/universal_platform.dart';
class JoinGameHandleWidget extends StatefulWidget {
final String passphrase;
const JoinGameHandleWidget({required this.passphrase, super.key});
@override
State<JoinGameHandleWidget> createState() => _JoinGameHandleWidgetState();
}
class _JoinGameHandleWidgetState extends State<JoinGameHandleWidget> {
late Future<GameInfo?> joinGameFuture;
@override
void initState() {
super.initState();
joinGameFuture = joinPrivateGame(widget.passphrase);
joinGameFuture.then((val) {
if (val == null) return;
ConnectionCubit.getInstance().connectIfNotConnected(
val.playerID!.uuid,
val.passphrase,
);
});
}
@override
Widget build(BuildContext context) {
FloatingActionButton? fltnBtn;
if (UniversalPlatform.isLinux ||
UniversalPlatform.isMacOS ||
UniversalPlatform.isWindows) {
fltnBtn = FloatingActionButton(
child: const Icon(Icons.cancel),
onPressed: () {
context.pop();
},
);
}
var loadingIndicator = const SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
);
return Scaffold(
floatingActionButton: fltnBtn,
body: Center(
child: FutureBuilder(
future: joinGameFuture,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return loadingIndicator;
} else {
return BlocBuilder<ConnectionCubit, ConnectionCubitState>(
builder: (context, state) {
if (state.iAmConnected) {
return const ChessGame();
} else {
return loadingIndicator;
}
});
}
}),
),
);
}
Future<GameInfo?> joinPrivateGame(String phrase) async {
http.Response response;
var existingInfo = await GameInfo.get(phrase);
log('playerID: ${existingInfo?.playerID} and passphrase: "${existingInfo?.passphrase}"');
GameInfo info;
if (existingInfo?.passphrase == phrase) {
// We have player info for this exact passphrase
info = GameInfo(playerID: existingInfo?.playerID, passphrase: phrase);
} else {
info = GameInfo(playerID: null, passphrase: phrase);
}
try {
response = await http.post(Uri.parse(config.getJoinGameURL()),
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: 1), () {
if (!mounted) return null;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.goNamed('lobbySelector'); // We go back to the lobby selector
});
return null;
}
if (response.statusCode == HttpStatus.notFound) {
const snackBar = SnackBar(
backgroundColor: Colors.amberAccent,
content: Text("Passphrase could not be found."),
);
if (!mounted) return null;
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
context.goNamed('lobbySelector');
return null;
}
if (response.statusCode == HttpStatus.ok) {
var info = GameInfo.fromJson(jsonDecode(response.body));
info.store();
log('Player info received from server: ');
log('playerID: ${info.playerID}');
log('passphrase: ${info.passphrase}');
return info;
}
return null;
}
}

View File

@ -1,10 +1,16 @@
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:mchess/utils/passphrase.dart';
class LobbySelector extends StatelessWidget { class LobbySelector extends StatefulWidget {
const LobbySelector({super.key}); const LobbySelector({super.key});
final buttonStyle = const ButtonStyle(); @override
State<LobbySelector> createState() => _LobbySelectorState();
}
class _LobbySelectorState extends State<LobbySelector> {
final phraseController = TextEditingController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -15,7 +21,7 @@ class LobbySelector extends StatelessWidget {
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
_dialogBuilder(context); context.goNamed('createGame');
}, },
child: const Row( child: const Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -24,7 +30,23 @@ class LobbySelector extends StatelessWidget {
SizedBox( SizedBox(
width: 10, width: 10,
), ),
Text('Private game') Text('Create private game')
],
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
buildEnterPassphraseDialog(context);
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.mail),
SizedBox(
width: 10,
),
Text('Join private game')
], ],
), ),
), ),
@ -34,28 +56,45 @@ class LobbySelector extends StatelessWidget {
); );
} }
Future<void> _dialogBuilder(BuildContext context) { Future<void> buildEnterPassphraseDialog(BuildContext context) {
return showDialog<void>( return showDialog<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return ScaffoldMessenger(
title: const Text('Host or join?'), child: Builder(builder: (builderContext) {
actions: <Widget>[ return Scaffold(
TextButton( backgroundColor: Colors.transparent,
child: const Text('Host'), body: AlertDialog(
onPressed: () { title: const Text('Enter the passphrase here:'),
context.push('/host'); content: TextField(
}, controller: phraseController,
), onSubmitted: (val) => submitAction(val),
TextButton( decoration: InputDecoration(
child: const Text('Join'), hintText: 'Enter passphrase here',
onPressed: () { suffixIcon: IconButton(
context.push('/join'); onPressed: () => submitAction(phraseController.text),
}, icon: const Icon(Icons.check),
), )),
], ),
actions: <Widget>[
TextButton(
child: const Text('Cancel'),
onPressed: () {
builderContext.pop();
},
),
],
),
);
}),
); );
}, },
); );
} }
void submitAction(String phrase) {
context.pop();
context.goNamed('game', pathParameters: {'phrase': phrase.toURL()});
phraseController.clear();
}
} }

View File

@ -1,91 +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/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() {
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,9 +1,13 @@
import 'dart:developer';
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/join_game_handle_widget.dart';
import 'package:mchess/pages/join_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_widget.dart';
import 'package:mchess/pages/prepare_random_game.dart'; import 'package:mchess/utils/passphrase.dart';
final navigatorKey = GlobalKey<NavigatorState>();
class ChessAppRouter { class ChessAppRouter {
static final ChessAppRouter _instance = ChessAppRouter._internal(); static final ChessAppRouter _instance = ChessAppRouter._internal();
@ -15,43 +19,38 @@ class ChessAppRouter {
} }
final router = GoRouter( final router = GoRouter(
navigatorKey: navigatorKey,
debugLogDiagnostics: true,
routes: [ routes: [
GoRoute( GoRoute(
path: '/', path: '/',
name: 'lobbySelector', name: 'lobbySelector',
builder: (context, state) => const LobbySelector(),
),
GoRoute(
path: '/prepareRandom',
name: 'prepareRandom',
builder: (context, state) {
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) { builder: (context, state) {
var args = state.extra as ChessGameArguments; return const LobbySelector();
return ChessGame(
lobbyID: args.lobbyID,
playerID: args.playerID,
passphrase: args.passphrase,
);
}, },
) routes: [
GoRoute(
path: 'createGame',
name: 'createGame',
builder: (context, state) {
return const CreateGameWidget();
}),
GoRoute(
path: 'game/:phrase',
name: 'game',
builder: (context, state) {
var urlPhrase = state.pathParameters['phrase'];
if (urlPhrase == null) {
log('in /game route builder: url phrase null');
return const LobbySelector();
}
return JoinGameHandleWidget(
passphrase: urlPhrase.toPhraseWithSpaces());
},
)
],
),
], ],
); );
} }

View File

@ -363,3 +363,26 @@ class PieceDragged {
PieceDragged(this.fromSquare, this.toSquare, this.movedPiece); PieceDragged(this.fromSquare, this.toSquare, this.movedPiece);
} }
bool isPromotionMove(
ChessPieceClass pieceMoved, ChessColor myColor, ChessCoordinate toSquare) {
bool isPromotion = false;
if (pieceMoved != ChessPieceClass.pawn) {
return isPromotion;
}
switch (myColor) {
case ChessColor.black:
if (toSquare.row == 1) {
isPromotion = true;
}
break;
case ChessColor.white:
if (toSquare.row == 8) {
isPromotion = true;
}
break;
}
return isPromotion;
}

34
lib/utils/config.dart Normal file
View File

@ -0,0 +1,34 @@
const prodURL = 'chess.sw-gross.de:9999';
const debugURL = 'localhost:8080';
const useDbgUrl = false;
String getCreateGameURL() {
var prot = 'https';
var domain = prodURL;
if (useDbgUrl) {
prot = 'http';
domain = debugURL;
}
return '$prot://$domain/api/hostPrivate';
}
String getJoinGameURL() {
var prot = 'https';
var domain = prodURL;
if (useDbgUrl) {
prot = 'http';
domain = debugURL;
}
return '$prot://$domain/api/joinPrivate';
}
String getWebsocketURL() {
var prot = 'wss';
var domain = prodURL;
if (useDbgUrl) {
prot = 'ws';
domain = debugURL;
}
return '$prot://$domain/api/ws';
}

30
lib/utils/passphrase.dart Normal file
View File

@ -0,0 +1,30 @@
extension PassphaseURL on String {
String capitalize() {
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
}
String toURL() {
var words = split(' ');
for (var i = 0; i < words.length; i++) {
words[i] = words[i].capitalize();
}
return words.join();
}
String toPhraseWithSpaces() {
var phrase = '';
for (var i = 0; i < length; i++) {
if (this[i] == this[i].toUpperCase()) {
phrase += ' ';
}
phrase += this[i].toLowerCase();
}
phrase = phrase.trim();
return phrase.toLowerCase();
}
}

View File

@ -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),
]),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:mchess/chess_bloc/promotion_bloc.dart'; import 'package:mchess/chess_bloc/promotion_bloc.dart';
import 'package:mchess/utils/chess_utils.dart'; import 'package:mchess/utils/chess_utils.dart';
@ -26,35 +27,31 @@ class PromotionDialog extends StatelessWidget {
children: [ children: [
IconButton( IconButton(
onPressed: () { onPressed: () {
Navigator.pop(context); pieceChosen(context, ChessPieceClass.queen);
pieceChosen(ChessPieceClass.queen);
}, },
icon: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey( icon: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey(
pieceClass: ChessPieceClass.queen, color: sideColor)]!), pieceClass: ChessPieceClass.queen, color: sideColor)]!),
iconSize: 200, iconSize: iconSize,
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
Navigator.pop(context); pieceChosen(context, ChessPieceClass.rook);
pieceChosen(ChessPieceClass.rook);
}, },
icon: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey( icon: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey(
pieceClass: ChessPieceClass.rook, color: sideColor)]!), pieceClass: ChessPieceClass.rook, color: sideColor)]!),
iconSize: 100, iconSize: iconSize,
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
Navigator.pop(context); pieceChosen(context, ChessPieceClass.knight);
pieceChosen(ChessPieceClass.knight);
}, },
icon: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey( icon: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey(
pieceClass: ChessPieceClass.knight, color: sideColor)]!), pieceClass: ChessPieceClass.knight, color: sideColor)]!),
iconSize: 10, iconSize: iconSize,
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
Navigator.pop(context); pieceChosen(context, ChessPieceClass.bishop);
pieceChosen(ChessPieceClass.bishop);
}, },
icon: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey( icon: SvgPicture.asset(chessPiecesAssets[ChessPieceAssetKey(
pieceClass: ChessPieceClass.bishop, color: sideColor)]!), pieceClass: ChessPieceClass.bishop, color: sideColor)]!),
@ -65,7 +62,8 @@ class PromotionDialog extends StatelessWidget {
); );
} }
void pieceChosen(ChessPieceClass pieceClass) { void pieceChosen(BuildContext context, ChessPieceClass pieceClass) {
context.pop();
PromotionBloc.getInstance() PromotionBloc.getInstance()
.add(PieceChosen(pieceClass: pieceClass, color: sideColor)); .add(PieceChosen(pieceClass: pieceClass, color: sideColor));
} }

View File

@ -5,6 +5,8 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -21,10 +21,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: bloc name: bloc
sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.2" version: "8.1.4"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -69,10 +69,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.8"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -81,6 +81,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -90,26 +114,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_bloc name: flutter_bloc
sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.3" version: "8.1.6"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "4.0.0"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338" sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" version: "2.0.10+1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -124,18 +148,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: go_router name: go_router
sha256: "5668e6d3dbcb2d0dfa25f7567554b88c57e1e3f3c440b672b24d4a9477017d5b" sha256: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.1.2" version: "14.2.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.2.1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -144,14 +168,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "4.0.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -164,26 +212,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.11.1"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.14.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -196,10 +244,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_parsing: path_parsing:
dependency: transitive dependency: transitive
description: description:
@ -208,22 +256,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.1" version: "6.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider: provider:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.5" version: "6.1.2"
quiver: quiver:
dependency: "direct main" dependency: "direct main"
description: description:
@ -232,6 +320,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "3.2.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -289,10 +433,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.7.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -301,38 +445,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.2"
universal_platform:
dependency: "direct main"
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
uuid: uuid:
dependency: "direct main" dependency: "direct main"
description: description:
name: uuid name: uuid
sha256: e03928880bdbcbf496fb415573f5ab7b1ea99b9b04f669c01104d085893c3134 sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.4.0"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics name: vector_graphics
sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f" sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.7" version: "1.1.11+1"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_codec name: vector_graphics_codec
sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f" sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.7" version: "1.1.11+1"
vector_graphics_compiler: vector_graphics_compiler:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_compiler name: vector_graphics_compiler
sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e" sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.7" version: "1.1.11+1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -341,30 +493,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "7475cb4dd713d57b6f7464c0e13f06da0d535d8b2067e188962a59bac2cf280b"
url: "https://pub.dev"
source: hosted
version: "14.2.2"
web: web:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.4-beta" version: "0.5.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
web_socket_channel: web_socket_channel:
dependency: "direct main" dependency: "direct main"
description: description:
name: web_socket_channel name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "3.0.0"
win32:
dependency: transitive
description:
name: win32
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
url: "https://pub.dev"
source: hosted
version: "5.5.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.4"
xml: xml:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.4.2" version: "6.5.0"
sdks: sdks:
dart: ">=3.1.0 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.22.0"

View File

@ -40,10 +40,12 @@ dependencies:
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
flutter_bloc: ^8.1.3 flutter_bloc: ^8.1.3
quiver: ^3.1.0 quiver: ^3.1.0
web_socket_channel: ^2.2.0 web_socket_channel: ^3.0.0
go_router: ^10.1.0 go_router: ^14.0.2
http: ^1.0.0 http: ^1.0.0
uuid: ^4.0.0 uuid: ^4.0.0
shared_preferences: ^2.2.2
universal_platform: ^1.0.0+1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -54,7 +56,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^2.0.0 flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View File

@ -8,16 +8,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mchess/pages/chess_game.dart'; import 'package:mchess/pages/chess_game.dart';
import 'package:uuid/uuid.dart';
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame. // Build our app and trigger a frame.
await tester.pumpWidget(const ChessGame( await tester.pumpWidget(const ChessGame());
playerID: UuidValue("test"),
lobbyID: UuidValue("testLobbyId"),
passphrase: 'test',
));
// Verify that our counter starts at 0. // Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget); expect(find.text('0'), findsOneWidget);