mchess-client/lib/utils/chess_utils.dart
Marco 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

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;
}