Marco
212a54612c
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
389 lines
9.9 KiB
Dart
389 lines
9.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_svg/svg.dart';
|
|
import 'package:mchess/api/move.dart';
|
|
import 'package:mchess/api/websocket_message.dart';
|
|
import 'package:quiver/core.dart';
|
|
|
|
Map<ChessPieceAssetKey, String> chessPiecesAssets = {
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.pawn,
|
|
color: ChessColor.white,
|
|
): 'assets/pieces/white/pawn.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.bishop,
|
|
color: ChessColor.white,
|
|
): 'assets/pieces/white/bishop.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.knight,
|
|
color: ChessColor.white,
|
|
): 'assets/pieces/white/knight.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.rook,
|
|
color: ChessColor.white,
|
|
): 'assets/pieces/white/rook.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.queen,
|
|
color: ChessColor.white,
|
|
): 'assets/pieces/white/queen.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.king,
|
|
color: ChessColor.white,
|
|
): 'assets/pieces/white/king.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.pawn,
|
|
color: ChessColor.black,
|
|
): 'assets/pieces/black/pawn.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.bishop,
|
|
color: ChessColor.black,
|
|
): 'assets/pieces/black/bishop.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.knight,
|
|
color: ChessColor.black,
|
|
): 'assets/pieces/black/knight.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.rook,
|
|
color: ChessColor.black,
|
|
): 'assets/pieces/black/rook.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.queen,
|
|
color: ChessColor.black,
|
|
): 'assets/pieces/black/queen.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.king,
|
|
color: ChessColor.black,
|
|
): 'assets/pieces/black/king.svg',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.none,
|
|
color: ChessColor.black,
|
|
): 'assets/empty.svg',
|
|
};
|
|
|
|
Map<ChessPieceAssetKey, String> chessPiecesShortName = {
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.pawn,
|
|
color: ChessColor.white,
|
|
): 'P',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.bishop,
|
|
color: ChessColor.white,
|
|
): 'B',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.knight,
|
|
color: ChessColor.white,
|
|
): 'N',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.rook,
|
|
color: ChessColor.white,
|
|
): 'R',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.queen,
|
|
color: ChessColor.white,
|
|
): 'Q',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.king,
|
|
color: ChessColor.white,
|
|
): 'K',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.pawn,
|
|
color: ChessColor.black,
|
|
): 'p',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.bishop,
|
|
color: ChessColor.black,
|
|
): 'b',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.knight,
|
|
color: ChessColor.black,
|
|
): 'n',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.rook,
|
|
color: ChessColor.black,
|
|
): 'r',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.queen,
|
|
color: ChessColor.black,
|
|
): 'q',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.king,
|
|
color: ChessColor.black,
|
|
): 'k',
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.none,
|
|
color: ChessColor.black,
|
|
): '-',
|
|
};
|
|
|
|
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): "♘",
|
|
ChessPieceAssetKey(
|
|
pieceClass: ChessPieceClass.bishop, color: ChessColor.white): "♗",
|
|
ChessPieceAssetKey(pieceClass: ChessPieceClass.king, color: ChessColor.white):
|
|
"♔",
|
|
ChessPieceAssetKey(
|
|
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 = {
|
|
'P': ChessPiece(ChessPieceClass.pawn, ChessColor.white),
|
|
'R': ChessPiece(ChessPieceClass.rook, ChessColor.white),
|
|
'N': ChessPiece(ChessPieceClass.knight, ChessColor.white),
|
|
'B': ChessPiece(ChessPieceClass.bishop, ChessColor.white),
|
|
'K': ChessPiece(ChessPieceClass.king, ChessColor.white),
|
|
'Q': ChessPiece(ChessPieceClass.queen, ChessColor.white),
|
|
'p': ChessPiece(ChessPieceClass.pawn, ChessColor.black),
|
|
'r': ChessPiece(ChessPieceClass.rook, ChessColor.black),
|
|
'n': ChessPiece(ChessPieceClass.knight, ChessColor.black),
|
|
'b': ChessPiece(ChessPieceClass.bishop, ChessColor.black),
|
|
'k': ChessPiece(ChessPieceClass.king, ChessColor.black),
|
|
'q': ChessPiece(ChessPieceClass.queen, ChessColor.black),
|
|
};
|
|
|
|
enum ChessColor {
|
|
black,
|
|
white;
|
|
|
|
ChessColor getOpposite() {
|
|
if (name == 'black') {
|
|
return white;
|
|
} else {
|
|
return black;
|
|
}
|
|
}
|
|
|
|
static ChessColor fromApiColor(ApiColor color) {
|
|
if (color == ApiColor.black) {
|
|
return black;
|
|
} else {
|
|
return white;
|
|
}
|
|
}
|
|
}
|
|
|
|
class ChessCoordinate {
|
|
final int column;
|
|
final int row;
|
|
|
|
ChessCoordinate(this.column, this.row);
|
|
|
|
ChessCoordinate.copyFrom(ChessCoordinate original)
|
|
: column = original.column,
|
|
row = original.row;
|
|
|
|
factory ChessCoordinate.fromApiCoordinate(ApiCoordinate apiCoordinate) {
|
|
return ChessCoordinate(apiCoordinate.col, apiCoordinate.row);
|
|
}
|
|
|
|
factory ChessCoordinate.fromString(String coordString) {
|
|
var column = int.parse(coordString[0]);
|
|
var row = int.parse(coordString[1]);
|
|
|
|
return ChessCoordinate(column, row);
|
|
}
|
|
|
|
factory ChessCoordinate.none() {
|
|
return ChessCoordinate(0, 0);
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
return hash2(column, row);
|
|
}
|
|
|
|
@override
|
|
operator ==(other) {
|
|
return other is ChessCoordinate &&
|
|
other.column == column &&
|
|
other.row == row;
|
|
}
|
|
|
|
String toAlphabetical() {
|
|
Map<int, String> columnMap = {
|
|
1: "a",
|
|
2: "b",
|
|
3: "c",
|
|
4: "d",
|
|
5: "e",
|
|
6: "f",
|
|
7: "g",
|
|
8: "h"
|
|
};
|
|
String rowStr = row.toString();
|
|
String colStr = columnMap[column]!;
|
|
|
|
return '$colStr$rowStr';
|
|
}
|
|
|
|
ApiCoordinate toApiCoordinate() {
|
|
return ApiCoordinate(col: column, row: row);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
String rowStr = row.toString();
|
|
String colStr = column.toString();
|
|
|
|
return '$colStr$rowStr';
|
|
}
|
|
|
|
static String columnIntToColumnString(int col) {
|
|
String colStr;
|
|
|
|
colStr = String.fromCharCode(col + 96);
|
|
|
|
return colStr;
|
|
}
|
|
|
|
static int columnStringToColumnInt(String col) {
|
|
int colInt;
|
|
colInt = col.codeUnitAt(0) - 96;
|
|
return colInt;
|
|
}
|
|
}
|
|
|
|
class ChessMove {
|
|
ChessCoordinate from;
|
|
ChessCoordinate to;
|
|
|
|
ChessMove({required this.from, required this.to});
|
|
|
|
factory ChessMove.fromApiMove(ApiMove apiMove) {
|
|
final start = ChessCoordinate.fromApiCoordinate(apiMove.startSquare);
|
|
final end = ChessCoordinate.fromApiCoordinate(apiMove.endSquare);
|
|
|
|
return ChessMove(from: start, to: end);
|
|
}
|
|
|
|
factory ChessMove.none() {
|
|
return ChessMove(from: ChessCoordinate(0, 0), to: ChessCoordinate(0, 0));
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
return hash2(from, to);
|
|
}
|
|
|
|
@override
|
|
operator ==(other) {
|
|
return other is ChessMove && other.from == from && other.to == to;
|
|
}
|
|
|
|
ApiMove toApiMove() {
|
|
var toSquare = to.toApiCoordinate();
|
|
var fromSquare = from.toApiCoordinate();
|
|
return ApiMove(
|
|
startSquare: fromSquare, endSquare: toSquare, promotionToPiece: null);
|
|
}
|
|
}
|
|
|
|
class ChessPiece extends StatelessWidget {
|
|
final ChessColor color;
|
|
final ChessPieceClass pieceClass;
|
|
final String shortName;
|
|
final Widget? pieceImage;
|
|
|
|
factory ChessPiece(ChessPieceClass pieceClass, ChessColor color) {
|
|
Widget? pieceImage;
|
|
String pieceAssetUrl = chessPiecesAssets[
|
|
ChessPieceAssetKey(pieceClass: pieceClass, color: color)]!;
|
|
String shortName = chessPiecesShortName[
|
|
ChessPieceAssetKey(pieceClass: pieceClass, color: color)]!;
|
|
|
|
pieceImage = SvgPicture.asset(pieceAssetUrl);
|
|
return ChessPiece._(pieceClass, color, pieceImage, shortName);
|
|
}
|
|
|
|
const ChessPiece.none({super.key})
|
|
: pieceClass = ChessPieceClass.none,
|
|
color = ChessColor.white,
|
|
pieceImage = null,
|
|
shortName = "-";
|
|
|
|
const ChessPiece._(
|
|
this.pieceClass, this.color, this.pieceImage, this.shortName);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SizedBox(
|
|
child: pieceImage,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ChessPieceAssetKey {
|
|
final ChessPieceClass pieceClass;
|
|
final ChessColor color;
|
|
|
|
ChessPieceAssetKey({required this.pieceClass, required this.color});
|
|
|
|
@override
|
|
int get hashCode {
|
|
return hash2(pieceClass, color);
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
return (other is ChessPieceAssetKey &&
|
|
(pieceClass == other.pieceClass) &&
|
|
(color == other.color));
|
|
}
|
|
}
|
|
|
|
enum ChessPieceClass {
|
|
none,
|
|
pawn,
|
|
bishop,
|
|
knight,
|
|
rook,
|
|
queen,
|
|
king,
|
|
}
|
|
|
|
class PieceDragged {
|
|
ChessCoordinate fromSquare;
|
|
ChessCoordinate toSquare;
|
|
ChessPiece? 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;
|
|
}
|