Compare commits

..

1 Commits

Author SHA1 Message Date
f5759cfbe6 Groundwork for move history 2023-09-04 22:00:58 +02:00
34 changed files with 873 additions and 1301 deletions

3
.gitignore vendored
View File

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

View File

@ -1 +0,0 @@
extensions:

View File

@ -1,65 +0,0 @@
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};
}

43
lib/api/register.dart Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ import 'package:mchess/chess/chess_square_inner_draggable.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/chess_bloc/tap_bloc.dart';
import 'package:mchess/utils/chess_utils.dart';
class ChessSquareOuterDragTarget extends StatelessWidget {
@ -11,36 +10,33 @@ class ChessSquareOuterDragTarget extends StatelessWidget {
final ChessPiece containedPiece;
const ChessSquareOuterDragTarget(
{super.key, required this.coordinate, required this.containedPiece});
{super.key,
required this.coordinate,
required this.containedPiece});
@override
Widget build(BuildContext context) {
return DragTarget<PieceDragged>(
onWillAcceptWithDetails: (details) {
if (details.data.fromSquare == coordinate) {
onWillAccept: (move) {
if (move?.fromSquare == coordinate) {
return false;
}
return true;
},
onAcceptWithDetails: (details) {
onAccept: (pieceDragged) {
// Replace the dummy value with the actual target of the move.
details.data.toSquare = coordinate;
pieceDragged.toSquare = coordinate;
TapBloc().add(CancelTapEvent());
if (isPromotionMove(
details.data.movedPiece!.pieceClass,
ChessBloc.getMyColor(),
details.data.toSquare,
)) {
if (isPromotionMove(pieceDragged)) {
var move = ChessMove(
from: details.data.fromSquare, to: details.data.toSquare);
PromotionBloc().add(PawnMovedToPromotionField(
move: move, colorMoved: ChessBloc.myColor));
} else if (coordinate != details.data.fromSquare) {
ChessBloc().add(OwnPieceMoved(
startSquare: details.data.fromSquare,
endSquare: details.data.toSquare));
from: pieceDragged.fromSquare, to: pieceDragged.toSquare);
PromotionBloc.getInstance().add(PawnMovedToPromotionField(
move: move, colorMoved: ChessBloc.myColor!));
} else if (coordinate != pieceDragged.fromSquare) {
ChessBloc.getInstance().add(OwnPieceMoved(
startSquare: pieceDragged.fromSquare,
endSquare: pieceDragged.toSquare,
piece: pieceDragged.movedPiece!));
}
},
builder: (context, candidateData, rejectedData) {
@ -51,4 +47,28 @@ 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> {
static final ChessBloc _instance = ChessBloc._internal();
static ChessColor turnColor = ChessColor.white;
static ChessColor myColor = ChessColor.white;
static ChessColor? myColor = ChessColor.white;
static ChessColor getMyColor() {
static ChessColor? getSidesColor() {
return myColor;
}
ChessBloc._internal() : super(ChessBoardState.init()) {
on<InitBoard>(initBoard);
on<ColorDetermined>(flipBoard);
on<ReceivedBoardState>(moveAndPositionHandler);
on<ReceivedMove>(moveAndPositionHandler);
on<OwnPieceMoved>(ownMoveHandler);
on<OwnPromotionPlayed>(ownPromotionHandler);
on<InvalidMovePlayed>(invalidMoveHandler);
@ -51,12 +51,10 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
void flipBoard(ColorDetermined event, Emitter<ChessBoardState> emit) {
log("My Color is $myColor");
myColor = event.playerColor;
myColor = event.myColor;
emit(
ChessBoardState(
event.playerColor,
event.myColor,
state.newTurnColor,
state.position,
ChessMove.none(),
@ -67,24 +65,21 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
}
void moveAndPositionHandler(
ReceivedBoardState event, Emitter<ChessBoardState> emit) {
ChessMove? move;
if (event.startSquare != ChessCoordinate.none() &&
event.endSquare != ChessCoordinate.none()) {
move = ChessMove(from: event.startSquare!, to: event.endSquare!);
ChessPositionManager.getInstance()
.recordMove(event.startSquare, event.endSquare, event.position);
}
myColor = event.playerColor;
turnColor = event.turnColor;
ReceivedMove event,
Emitter<ChessBoardState> emit,
) {
ChessPositionManager.getInstance()
.recordMove(event.startSquare, event.endSquare, event.position);
turnColor = state.newTurnColor == ChessColor.white
? ChessColor.black
: ChessColor.white;
emit(
ChessBoardState(
myColor,
event.turnColor,
state.bottomColor,
turnColor,
event.position,
move,
ChessMove(from: event.startSquare, to: event.endSquare),
true,
event.squareInCheck,
),
@ -98,11 +93,10 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
var apiMessage = ApiWebsocketMessage(
type: MessageType.move,
move: apiMove,
turnColor: null,
color: null,
reason: null,
position: null,
squareInCheck: null,
playerColor: null,
);
ServerConnection.getInstance().send(jsonEncode(apiMessage));
@ -114,14 +108,8 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
tempPosition[move.from] = const ChessPiece.none();
emit(
ChessBoardState(
state.bottomColor,
turnColor,
tempPosition,
ChessPositionManager.getInstance().lastMove,
true,
ChessCoordinate.none(),
),
ChessBoardState(state.bottomColor, turnColor, tempPosition, move, false,
ChessCoordinate.none()),
);
}
@ -129,16 +117,15 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
OwnPromotionPlayed event, Emitter<ChessBoardState> emit) {
var apiMove = event.move.toApiMove();
var shorNameForPiece = chessPiecesShortName[
ChessPieceAssetKey(pieceClass: event.pieceClass, color: myColor)]!;
ChessPieceAssetKey(pieceClass: event.pieceClass, color: myColor!)]!;
apiMove.promotionToPiece = shorNameForPiece;
var message = ApiWebsocketMessage(
type: MessageType.move,
move: apiMove,
turnColor: null,
color: null,
reason: null,
position: null,
squareInCheck: null,
playerColor: null,
);
log(jsonEncode(message));
ServerConnection.getInstance().send(jsonEncode(message));
@ -146,14 +133,13 @@ class ChessBloc extends Bloc<ChessEvent, ChessBoardState> {
void invalidMoveHandler(
InvalidMovePlayed event, Emitter<ChessBoardState> emit) {
var move = ChessPositionManager.getInstance().lastMove;
emit(
ChessBoardState(
state.bottomColor,
turnColor,
ChessPositionManager.getInstance().currentPosition,
move,
true,
ChessMove.none(),
false,
event.squareInCheck,
),
);
@ -165,7 +151,7 @@ class ChessBoardState {
final ChessColor newTurnColor;
final ChessPosition position;
final ChessMove lastMove;
final bool colorLastMove;
final bool positionAckdByServer;
final ChessCoordinate squareInCheck;
ChessBoardState._(
@ -173,7 +159,7 @@ class ChessBoardState {
this.newTurnColor,
this.position,
this.lastMove,
this.colorLastMove,
this.positionAckdByServer,
this.squareInCheck,
);
@ -181,12 +167,12 @@ class ChessBoardState {
ChessColor bottomColor,
ChessColor turnColor,
ChessPosition position,
ChessMove? lastMove,
ChessMove lastMove,
bool positionAckd,
ChessCoordinate squareInCheck,
) {
return ChessBoardState._(bottomColor, turnColor, position,
lastMove ?? ChessMove.none(), positionAckd, squareInCheck);
return ChessBoardState._(bottomColor, turnColor, position, lastMove,
positionAckd, squareInCheck);
}
factory ChessBoardState.init() {

View File

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

View File

@ -23,6 +23,7 @@ class ChessPositionManager {
if (history.isEmpty) return null;
return history.last;
}
ChessMoveHistory get allMoves => history;
ChessPosition fromPGNString(String pgn) {
ChessPosition pos = {};
@ -71,16 +72,10 @@ class ChessPositionManager {
log(logString);
}
void recordMove(
ChessCoordinate? from, ChessCoordinate? to, ChessPosition pos) {
void recordMove(ChessCoordinate from, ChessCoordinate to, ChessPosition pos) {
position = pos;
history.add(
ChessMove(
from: from ?? ChessCoordinate.none(),
to: to ?? ChessCoordinate.none(),
),
);
history.add(ChessMove(from: from, to: to));
logPosition(position);
logHistory(history);
@ -95,15 +90,6 @@ class ChessPositionManager {
return _instance;
}
static ChessPosition _getDebugPostion() {
ChessPosition pos = {};
pos[ChessCoordinate(7, 7)] =
ChessPiece(ChessPieceClass.pawn, ChessColor.white);
return pos;
}
static ChessPosition _getStartingPosition() {
ChessPosition pos = {};

View File

@ -1,109 +0,0 @@
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,21 +1,20 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter/foundation.dart';
import 'package:mchess/api/move.dart';
import 'package:mchess/api/websocket_message.dart';
import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess_bloc/chess_events.dart';
import 'package:mchess/api/game_info.dart';
import 'package:mchess/api/register.dart';
import 'package:mchess/chess_bloc/chess_position.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/config.dart' as config;
import 'package:web_socket_channel/web_socket_channel.dart';
class ServerConnection {
WebSocketChannel? channel;
late WebSocketChannel channel;
late bool wasConnected = false;
late int counter = 0;
Stream broadcast = const Stream.empty();
static final ServerConnection _instance = ServerConnection._internal();
@ -33,87 +32,71 @@ class ServerConnection {
}
void send(String message) {
if (channel == null) {
log("Sending on channel without initializing");
return;
channel.sink.add(message);
counter++;
}
void connect(String playerID, lobbyID, String? passphrase) {
if (kDebugMode) {
channel =
WebSocketChannel.connect(Uri.parse('ws://localhost:8080/api/ws'));
} else {
channel = WebSocketChannel.connect(
Uri.parse('wss://chess.sw-gross.de:9999/api/ws'));
}
channel!.sink.add(message);
}
Future? connect(String playerID, String? passphrase) {
if (channel != null) return null;
channel = WebSocketChannel.connect(Uri.parse(config.getWebsocketURL()));
channel!.ready.then((val) {
send(
jsonEncode(
WebsocketMessageIdentifyPlayer(
playerID: (playerID),
passphrase: (passphrase),
),
send(
jsonEncode(
WebsocketMessageIdentifyPlayer(
playerID: (playerID),
lobbyID: (lobbyID),
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();
log(channel.closeCode.toString());
broadcast = channel.stream.asBroadcastStream();
broadcast.listen(handleIncomingData);
}
void handleIncomingData(dynamic data) {
log('${DateTime.now()}: Data received:');
log("Data received:");
log(data);
var apiMessage = ApiWebsocketMessage.fromJson(jsonDecode(data));
switch (apiMessage.type) {
case MessageType.boardState:
handleBoardStateMessage(apiMessage);
break;
case MessageType.colorDetermined:
handleIncomingColorDeterminedMessage(apiMessage);
break;
case MessageType.move:
log('ERROR: move message received');
handleIncomingMoveMessage(apiMessage);
break;
case MessageType.invalidMove:
handleInvalidMoveMessage(apiMessage);
case MessageType.gameEnded:
handleGameEndedMessage(apiMessage);
}
}
void handleBoardStateMessage(ApiWebsocketMessage apiMessage) {
ChessMove? move;
if (apiMessage.move != null) {
move = ChessMove.fromApiMove(apiMessage.move!);
}
void handleIncomingColorDeterminedMessage(ApiWebsocketMessage apiMessage) {
ConnectionCubit.getInstance().opponentConnected();
ChessBloc.getInstance().add(InitBoard());
ChessBloc.getInstance().add(
ColorDetermined(myColor: ChessColor.fromApiColor(apiMessage.color!)));
}
void handleIncomingMoveMessage(ApiWebsocketMessage apiMessage) {
var move = ChessMove.fromApiMove(apiMessage.move!);
if (apiMessage.position != null) {
ChessBloc.getInstance().add(
ReceivedBoardState(
startSquare: move?.from,
endSquare: move?.to,
ReceivedMove(
startSquare: move.from,
endSquare: move.to,
position: ChessPositionManager.getInstance()
.fromPGNString(apiMessage.position!),
squareInCheck: ChessCoordinate.fromApiCoordinate(
apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
turnColor: ChessColor.fromApiColor(apiMessage.turnColor!),
playerColor: ChessColor.fromApiColor(apiMessage.playerColor!),
squareInCheck:
ChessCoordinate.fromApiCoordinate(apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
),
);
} else {
@ -121,46 +104,14 @@ 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) {
log("invalid move message received, with move: ${apiMessage.move.toString()}");
ChessBloc.getInstance().add(
InvalidMovePlayed(
move: ChessMove.fromApiMove(apiMessage.move!),
squareInCheck: ChessCoordinate.fromApiCoordinate(
apiMessage.squareInCheck ?? const ApiCoordinate(col: 0, row: 0)),
squareInCheck:
ChessCoordinate.fromApiCoordinate(apiMessage.squareInCheck!),
),
);
}
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,64 +15,21 @@ class ConnectionCubit extends Cubit<ConnectionCubitState> {
return _instance;
}
void connect(String playerID, String? 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 connect(String playerID, lobbyID, String? passphrase) {
ServerConnection.getInstance().connect(playerID, lobbyID, passphrase);
}
void opponentConnected() {
emit(ConnectionCubitState(
iAmConnected: state.iAmConnected,
connectedToPhrase: state.connectedToPhrase,
opponentConnected: true));
emit(ConnectionCubitState(true));
}
}
class ConnectionCubitState {
final bool iAmConnected;
final String? connectedToPhrase;
final bool opponentConnected;
ConnectionCubitState(
{required this.iAmConnected,
required this.connectedToPhrase,
required this.opponentConnected});
ConnectionCubitState(this.opponentConnected);
factory ConnectionCubitState.init() {
return ConnectionCubitState(
iAmConnected: false, connectedToPhrase: null, opponentConnected: false);
return ConnectionCubitState(false);
}
}

View File

@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mchess/chess/chess_app.dart';
void main() {
GoRouter.optionURLReflectsImperativeAPIs = true;
runApp(const ChessApp());
}

View File

@ -5,64 +5,74 @@ import 'package:mchess/chess_bloc/chess_bloc.dart';
import 'package:mchess/chess/chess_board.dart';
import 'package:mchess/chess_bloc/promotion_bloc.dart';
import 'package:mchess/chess_bloc/tap_bloc.dart';
import 'package:mchess/utils/chess_utils.dart';
import 'package:mchess/utils/widgets/move_history_widget.dart';
import 'package:mchess/utils/widgets/promotion_dialog.dart';
import 'package:universal_platform/universal_platform.dart';
import 'package:uuid/uuid.dart';
class ChessGame extends StatefulWidget {
const ChessGame({super.key});
final UuidValue playerID;
final UuidValue lobbyID;
final String? passphrase;
const ChessGame(
{required this.playerID,
required this.lobbyID,
required this.passphrase,
super.key});
@override
State<ChessGame> createState() => _ChessGameState();
}
class _ChessGameState extends State<ChessGame> {
ChessCoordinate? tappedSquare;
@override
void initState() {
super.initState();
}
@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: Container(
margin: const EdgeInsets.all(10),
child: BlocListener<TapBloc, TapState>(
child: BlocListener<PromotionBloc, PromotionState>(
listener: (context, state) {
setState(() {
tappedSquare = state.firstSquareTapped;
});
if (state.showPromotionDialog) {
promotionDialogBuilder(context, state.colorMoved);
}
},
child: BlocListener<PromotionBloc, PromotionState>(
listener: (listenerContext, state) {
if (state.showPromotionDialog) {
promotionDialogBuilder(listenerContext, state.colorMoved);
}
child: BlocBuilder<ChessBloc, ChessBoardState>(
builder: (context, state) {
return Row(
children: [
Expanded(
flex: 1,
child: Container(),
),
Expanded(
flex: 3,
child: ChessBoard(boardState: state),
),
const Expanded(
flex: 1,
child: Align(
alignment: Alignment.centerLeft,
child: MoveHistory()),
),
],
);
},
child: BlocBuilder<ChessBloc, ChessBoardState>(
builder: (context, state) {
return ChessBoard(
boardState: state,
tappedSquare: tappedSquare,
);
},
),
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.push('/');
},
child: const Icon(Icons.cancel),
),
);
}
@ -89,18 +99,4 @@ class ChessGameArguments {
required this.playerID,
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

@ -1,161 +0,0 @@
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;
}
}

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

@ -0,0 +1,140 @@
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;
}
}

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

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

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

View File

@ -0,0 +1,91 @@
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,13 +1,9 @@
import 'dart:developer';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:mchess/pages/join_game_handle_widget.dart';
import 'package:mchess/pages/chess_game.dart';
import 'package:mchess/pages/join_game.dart';
import 'package:mchess/pages/lobby_selector.dart';
import 'package:mchess/pages/create_game_widget.dart';
import 'package:mchess/utils/passphrase.dart';
final navigatorKey = GlobalKey<NavigatorState>();
import 'package:mchess/pages/host_game.dart';
import 'package:mchess/pages/prepare_random_game.dart';
class ChessAppRouter {
static final ChessAppRouter _instance = ChessAppRouter._internal();
@ -19,38 +15,43 @@ class ChessAppRouter {
}
final router = GoRouter(
navigatorKey: navigatorKey,
debugLogDiagnostics: true,
routes: [
GoRoute(
path: '/',
name: 'lobbySelector',
builder: (context, state) {
return const LobbySelector();
},
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());
},
)
],
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) {
var args = state.extra as ChessGameArguments;
return ChessGame(
lobbyID: args.lobbyID,
playerID: args.playerID,
passphrase: args.passphrase,
);
},
)
],
);
}

View File

@ -115,30 +115,54 @@ Map<ChessPieceAssetKey, String> chessPiecesShortName = {
};
Map<ChessPieceAssetKey, String> pieceCharacter = {
ChessPieceAssetKey(pieceClass: ChessPieceClass.pawn, color: ChessColor.white):
"",
ChessPieceAssetKey(pieceClass: ChessPieceClass.rook, color: ChessColor.white):
"",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.knight, color: ChessColor.white): "",
pieceClass: ChessPieceClass.pawn,
color: ChessColor.white,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.bishop, color: ChessColor.white): "",
ChessPieceAssetKey(pieceClass: ChessPieceClass.king, color: ChessColor.white):
"",
pieceClass: ChessPieceClass.rook,
color: ChessColor.white,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.queen, color: ChessColor.white): "",
ChessPieceAssetKey(pieceClass: ChessPieceClass.pawn, color: ChessColor.black):
"♟︎",
ChessPieceAssetKey(pieceClass: ChessPieceClass.rook, color: ChessColor.black):
"",
pieceClass: ChessPieceClass.knight,
color: ChessColor.white,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.knight, color: ChessColor.black): "",
pieceClass: ChessPieceClass.bishop,
color: ChessColor.white,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.bishop, color: ChessColor.black): "",
ChessPieceAssetKey(pieceClass: ChessPieceClass.king, color: ChessColor.black):
"",
pieceClass: ChessPieceClass.king,
color: ChessColor.white,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.queen, color: ChessColor.black): "",
pieceClass: ChessPieceClass.queen,
color: ChessColor.white,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.pawn,
color: ChessColor.black,
): "♟︎",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.rook,
color: ChessColor.black,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.knight,
color: ChessColor.black,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.bishop,
color: ChessColor.black,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.king,
color: ChessColor.black,
): "",
ChessPieceAssetKey(
pieceClass: ChessPieceClass.queen,
color: ChessColor.black,
): "",
};
Map<String, ChessPiece> pieceFromShortname = {
@ -363,26 +387,3 @@ class PieceDragged {
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;
}

View File

@ -1,34 +0,0 @@
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';
}

View File

@ -1,30 +0,0 @@
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

@ -0,0 +1,89 @@
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> {
ChessMoveHistory allMoves = [];
List<HistoryEntry> entries = [];
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<ChessBloc, ChessBoardState>(builder: (context, state) {
//We do not use the state, we just use the ChessBloc as a trigger
var positionManager = ChessPositionManager.getInstance();
if (positionManager.lastMove == null) return Container();
var pieceMoved = positionManager.getPieceAt(positionManager.lastMove!.to);
var pieceChar = pieceCharacter[ChessPieceAssetKey(
//we take the opposite color because we use dark theme. White pieces appear black and vice versa.
pieceClass: pieceMoved!.pieceClass,
color: pieceMoved.color.getOpposite())];
if (pieceMoved.color == ChessColor.white) {
var entry = HistoryEntry();
entry.setWhite(
pieceChar!, positionManager.lastMove!.to.toAlphabetical());
entries.add(entry);
} else {
var entry = entries.last;
entry.setBlack(
pieceChar!, positionManager.lastMove!.to.toAlphabetical());
}
return ListView.builder(
itemCount: entries.length,
itemBuilder: (context, index) {
return Text(
entries[index].toString(),
style: const TextStyle(fontSize: 20),
);
},
);
});
}
}
class HistoryEntry {
String? wChar;
String? wTo;
String? bChar;
String? bTo;
HistoryEntry();
void setWhite(String char, to) {
wChar = char;
wTo = to;
}
void setBlack(String char, to) {
bChar = char;
bTo = to;
}
@override
String toString() {
String entry = "";
if (wChar != null) {
entry = "$entry$wChar$wTo";
}
if (bChar != null) {
entry = "$entry\t\t$bChar$bTo";
}
return entry;
}
}

View File

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

View File

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

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: args
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.4.2"
async:
dependency: transitive
description:
@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
version: "8.1.2"
boolean_selector:
dependency: transitive
description:
@ -69,10 +69,10 @@ packages:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.8"
version: "1.0.6"
fake_async:
dependency: transitive
description:
@ -81,30 +81,6 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description: flutter
@ -114,26 +90,26 @@ packages:
dependency: "direct main"
description:
name: flutter_bloc
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae
url: "https://pub.dev"
source: hosted
version: "8.1.6"
version: "8.1.3"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "2.0.3"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338"
url: "https://pub.dev"
source: hosted
version: "2.0.10+1"
version: "2.0.7"
flutter_test:
dependency: "direct dev"
description: flutter
@ -148,18 +124,18 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554
sha256: "5668e6d3dbcb2d0dfa25f7567554b88c57e1e3f3c440b672b24d4a9477017d5b"
url: "https://pub.dev"
source: hosted
version: "14.2.0"
version: "10.1.2"
http:
dependency: "direct main"
description:
name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.1.0"
http_parser:
dependency: transitive
description:
@ -168,38 +144,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "2.1.1"
logging:
dependency: transitive
description:
@ -212,26 +164,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.5.0"
meta:
dependency: transitive
description:
name: meta
sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b"
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
version: "1.14.0"
version: "1.9.1"
nested:
dependency: transitive
description:
@ -244,10 +196,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.8.3"
path_parsing:
dependency: transitive
description:
@ -256,62 +208,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6
url: "https://pub.dev"
source: hosted
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"
version: "6.0.1"
provider:
dependency: transitive
description:
name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
url: "https://pub.dev"
source: hosted
version: "6.1.2"
version: "6.0.5"
quiver:
dependency: "direct main"
description:
@ -320,62 +232,6 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description: flutter
@ -433,10 +289,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794"
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.7.1"
version: "0.6.1"
typed_data:
dependency: transitive
description:
@ -445,46 +301,38 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
name: uuid
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
sha256: e03928880bdbcbf496fb415573f5ab7b1ea99b9b04f669c01104d085893c3134
url: "https://pub.dev"
source: hosted
version: "4.4.0"
version: "4.0.0"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
version: "1.1.7"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
version: "1.1.7"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
version: "1.1.7"
vector_math:
dependency: transitive
description:
@ -493,62 +341,30 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.5.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
version: "0.1.4-beta"
web_socket_channel:
dependency: "direct main"
description:
name: web_socket_channel
sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
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"
version: "2.4.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556
url: "https://pub.dev"
source: hosted
version: "6.5.0"
version: "6.4.2"
sdks:
dart: ">=3.4.0 <4.0.0"
flutter: ">=3.22.0"
dart: ">=3.1.0 <4.0.0"
flutter: ">=3.7.0"

View File

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

View File

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