123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- import 'dart:async';
- import 'package:cchess/cchess.dart';
- import 'package:shirne_dialog/shirne_dialog.dart';
- import 'package:flutter/material.dart';
- import 'action_dialog.dart';
- import 'board.dart';
- import 'piece.dart';
- import 'chess_pieces.dart';
- import 'mark_component.dart';
- import 'point_component.dart';
- import '../global.dart';
- import '../models/game_event.dart';
- import '../models/sound.dart';
- import '../models/game_manager.dart';
- import '../driver/player_driver.dart';
- import '../pages/home_page.dart';
- class Chess extends StatefulWidget {
- final String skin;
- const Chess({Key? key, this.skin = 'woods'}) : super(key: key);
- @override
- State<Chess> createState() => ChessState();
- }
- class ChessState extends State<Chess> {
- // 当前激活的子
- ChessItem? activeItem;
- double skewStepper = 0;
- // 被吃的子
- ChessItem? dieFlash;
- // 刚走过的位置
- String lastPosition = 'a0';
- // 可落点,包括吃子点
- List<String> movePoints = [];
- bool isInit = false;
- late GameManager gamer;
- bool isLoading = true;
- // 棋局初始化时所有的子力
- List<ChessItem> items = [];
- @override
- void initState() {
- super.initState();
- initGamer();
- }
- void initGamer() {
- if (isInit) return;
- isInit = true;
- HomePageState? gameWrapper =
- context.findAncestorStateOfType<HomePageState>();
- if (gameWrapper == null) return;
- gamer = gameWrapper.gamer;
- gamer.on<GameLoadEvent>(reloadGame);
- gamer.on<GameResultEvent>(onResult);
- gamer.on<GameMoveEvent>(onMove);
- gamer.on<GameFlipEvent>(onFlip);
- reloadGame(GameLoadEvent(0));
- }
- @override
- void dispose() {
- gamer.off<GameLoadEvent>(reloadGame);
- gamer.off<GameResultEvent>(onResult);
- gamer.off<GameMoveEvent>(onMove);
- super.dispose();
- }
- void onFlip(GameEvent event) {
- setState(() {});
- }
- void onResult(GameEvent event) {
- if (event.data == null || event.data!.isEmpty) {
- return;
- }
- List<String> parts = event.data.split(' ');
- String? resultText =
- (parts.length > 1 && parts[1].isNotEmpty) ? parts[1] : null;
- switch (parts[0]) {
- case 'checkMate':
- //toast(context.l10n.check);
- showAction(ActionType.checkMate);
- break;
- case 'eat':
- showAction(ActionType.eat);
- break;
- case ChessManual.resultFstLoose:
- alertResult(resultText ?? context.l10n.redLoose);
- break;
- case ChessManual.resultFstWin:
- alertResult(resultText ?? context.l10n.redWin);
- break;
- case ChessManual.resultFstDraw:
- alertResult(resultText ?? context.l10n.redDraw);
- break;
- default:
- break;
- }
- }
- void reloadGame(GameEvent event) {
- if (event.data < -1) {
- return;
- }
- if (event.data < 0) {
- if (!isLoading) {
- setState(() {
- isLoading = true;
- });
- }
- return;
- }
- setState(() {
- items = gamer.manual.getChessItems();
- isLoading = false;
- lastPosition = '';
- activeItem = null;
- });
- String position = gamer.lastMove;
- if (position.isNotEmpty) {
- logger.info('last move $position');
- Future.delayed(const Duration(milliseconds: 32)).then((value) {
- setState(() {
- lastPosition = position.substring(0, 2);
- ChessPos activePos = ChessPos.fromCode(position.substring(2, 4));
- activeItem = items.firstWhere(
- (item) =>
- !item.isBlank &&
- item.position == ChessPos.fromCode(lastPosition),
- orElse: () => ChessItem('0'),
- );
- activeItem!.position = activePos;
- });
- });
- }
- }
- void addStep(ChessPos chess, ChessPos next) {
- gamer.addStep(chess, next);
- }
- Future<void> fetchMovePoints() async {
- setState(() {
- movePoints = gamer.rule.movePoints(activeItem!.position);
- // print('move points: $movePoints');
- });
- }
- /// 从外部过来的指令
- void onMove(GameEvent event) {
- String move = event.data!;
- logger.info('onmove $move');
- if (move.isEmpty) return;
- if (move == PlayerDriver.rstGiveUp) return;
- if (move.contains(PlayerDriver.rstRqstDraw)) {
- toast(
- context.l10n.requestDraw,
- SnackBarAction(
- label: context.l10n.agreeToDraw,
- onPressed: () {
- gamer.player.completeMove(PlayerDriver.rstDraw);
- },
- ),
- 5,
- );
- move = move.replaceAll(PlayerDriver.rstRqstDraw, '').trim();
- if (move.isEmpty) {
- return;
- }
- }
- if (move == PlayerDriver.rstRqstRetract) {
- confirm(
- context.l10n.requestRetract,
- context.l10n.agreeRetract,
- context.l10n.disagreeRetract,
- ).then((bool? isAgree) {
- gamer.player
- .completeMove(isAgree == true ? PlayerDriver.rstRetract : '');
- });
- return;
- }
- ChessPos fromPos = ChessPos.fromCode(move.substring(0, 2));
- ChessPos toPosition = ChessPos.fromCode(move.substring(2, 4));
- activeItem = items.firstWhere(
- (item) => !item.isBlank && item.position == fromPos,
- orElse: () => ChessItem('0'),
- );
- ChessItem newActive = items.firstWhere(
- (item) => !item.isBlank && item.position == toPosition,
- orElse: () => ChessItem('0'),
- );
- setState(() {
- if (activeItem != null && !activeItem!.isBlank) {
- logger.info('$activeItem => $move');
- activeItem!.position = ChessPos.fromCode(move.substring(2, 4));
- lastPosition = fromPos.toCode();
- if (!newActive.isBlank) {
- logger.info('eat $newActive');
- //showAction(ActionType.eat);
- // 被吃的子的快照
- dieFlash = ChessItem(newActive.code, position: toPosition);
- newActive.isDie = true;
- Future.delayed(const Duration(milliseconds: 250), () {
- setState(() {
- dieFlash = null;
- });
- });
- }
- } else {
- logger.info('Remote move error $move');
- }
- });
- }
- void animateMove(ChessPos nextPosition) {
- logger.info('$activeItem => $nextPosition');
- setState(() {
- activeItem!.position = nextPosition.copy();
- });
- }
- void clearActive() {
- setState(() {
- activeItem = null;
- lastPosition = '';
- });
- }
- /// 检测用户的输入位置是否有效
- Future<bool> checkCanMove(
- String activePos,
- ChessPos toPosition, [
- ChessItem? toItem,
- ]) async {
- if (!movePoints.contains(toPosition.toCode())) {
- if (toItem != null) {
- toast('can\'t eat ${toItem.code} at $toPosition');
- } else {
- toast('can\'t move to $toPosition');
- }
- return false;
- }
- String move = activePos + toPosition.toCode();
- ChessRule rule = ChessRule(gamer.fen.copy());
- rule.fen.move(move);
- if (rule.isKingMeet(gamer.curHand)) {
- toast(context.l10n.cantSendCheck);
- return false;
- }
- // 区分应将和送将
- if (rule.isCheck(gamer.curHand)) {
- if (gamer.isCheckMate) {
- toast(context.l10n.plsParryCheck);
- } else {
- toast(context.l10n.cantSendCheck);
- }
- return false;
- }
- return true;
- }
- /// 用户点击棋盘位置的反馈 包括选中子,走子,吃子,无反馈
- bool onPointer(ChessPos toPosition) {
- ChessItem newActive = items.firstWhere(
- (item) => !item.isBlank && item.position == toPosition,
- orElse: () => ChessItem('0'),
- );
- int ticker = DateTime.now().millisecondsSinceEpoch;
- if (newActive.isBlank) {
- if (activeItem != null && activeItem!.team == gamer.curHand) {
- String activePos = activeItem!.position.toCode();
- animateMove(toPosition);
- checkCanMove(activePos, toPosition).then((canMove) {
- int delay = 250 - (DateTime.now().millisecondsSinceEpoch - ticker);
- if (delay < 0) {
- delay = 0;
- }
- if (canMove) {
- // 立即更新的部分
- setState(() {
- // 清掉落子点
- movePoints = [];
- lastPosition = activePos;
- });
- addStep(ChessPos.fromCode(activePos), toPosition);
- } else {
- Future.delayed(Duration(milliseconds: delay), () {
- setState(() {
- activeItem!.position = ChessPos.fromCode(activePos);
- });
- });
- }
- });
- return true;
- }
- return false;
- }
- // 置空当前选中状态
- if (activeItem != null && activeItem!.position == toPosition) {
- Sound.play(Sound.click);
- setState(() {
- activeItem = null;
- lastPosition = '';
- movePoints = [];
- });
- } else if (newActive.team == gamer.curHand) {
- Sound.play(Sound.click);
- // 切换选中的子
- setState(() {
- activeItem = newActive;
- lastPosition = '';
- movePoints = [];
- });
- fetchMovePoints();
- return true;
- } else {
- // 吃对方的子
- if (activeItem != null && activeItem!.team == gamer.curHand) {
- String activePos = activeItem!.position.toCode();
- animateMove(toPosition);
- checkCanMove(activePos, toPosition, newActive).then((canMove) {
- int delay = 250 - (DateTime.now().millisecondsSinceEpoch - ticker);
- if (delay < 0) {
- delay = 0;
- }
- if (canMove) {
- addStep(ChessPos.fromCode(activePos), toPosition);
- //showAction(ActionType.eat);
- setState(() {
- // 清掉落子点
- movePoints = [];
- lastPosition = activePos;
- // 被吃的子的快照
- dieFlash = ChessItem(newActive.code, position: toPosition);
- newActive.isDie = true;
- });
- Future.delayed(Duration(milliseconds: delay), () {
- setState(() {
- dieFlash = null;
- });
- });
- } else {
- Future.delayed(Duration(milliseconds: delay), () {
- setState(() {
- activeItem!.position = ChessPos.fromCode(activePos);
- });
- });
- }
- });
- return true;
- }
- }
- return false;
- }
- ChessPos pointTrans(Offset tapPoint) {
- int x = (tapPoint.dx - gamer.skin.offset.dx * gamer.scale) ~/
- (gamer.skin.size * gamer.scale);
- int y = 9 -
- (tapPoint.dy - gamer.skin.offset.dy * gamer.scale) ~/
- (gamer.skin.size * gamer.scale);
- return ChessPos(gamer.isFlip ? 8 - x : x, gamer.isFlip ? 9 - y : y);
- }
- void toast(String message, [SnackBarAction? action, int duration = 3]) {
- MyDialog.snack(
- message,
- action: action,
- duration: Duration(seconds: duration),
- );
- }
- void alertResult(message) {
- confirm(message, context.l10n.oneMoreGame, context.l10n.letMeSee)
- .then((isConfirm) {
- if (isConfirm ?? false) {
- gamer.newGame();
- }
- });
- }
- Future<bool?> confirm(String message, String agreeText, String cancelText) {
- return MyDialog.confirm(
- message,
- buttonText: agreeText,
- cancelText: cancelText,
- );
- }
- Future<bool?> alert(String message) async {
- return MyDialog.alert(message);
- }
- // 显示吃/将效果
- void showAction(ActionType type) {
- final overlay = Overlay.of(context);
- late OverlayEntry entry;
- entry = OverlayEntry(
- builder: (context) => Center(
- child: ActionDialog(
- type,
- delay: 2,
- onHide: () {
- entry.remove();
- },
- ),
- ),
- );
- overlay.insert(entry);
- }
- @override
- Widget build(BuildContext context) {
- initGamer();
- if (isLoading) {
- return const Center(
- child: CircularProgressIndicator(),
- );
- }
- List<Widget> widgets = [const Board()];
- List<Widget> layer0 = [];
- if (dieFlash != null) {
- layer0.add(
- Align(
- alignment: gamer.skin.getAlign(dieFlash!.position),
- child: Piece(item: dieFlash!, isActive: false, isAblePoint: false),
- ),
- );
- }
- if (lastPosition.isNotEmpty) {
- ChessItem emptyItem =
- ChessItem('0', position: ChessPos.fromCode(lastPosition));
- layer0.add(
- Align(
- alignment: gamer.skin.getAlign(emptyItem.position),
- child: MarkComponent(
- size: gamer.skin.size * gamer.scale,
- ),
- ),
- );
- }
- widgets.add(
- Stack(
- alignment: Alignment.center,
- fit: StackFit.expand,
- children: layer0,
- ),
- );
- widgets.add(
- ChessPieces(
- items: items,
- activeItem: activeItem,
- ),
- );
- List<Widget> layer2 = [];
- for (var element in movePoints) {
- ChessItem emptyItem =
- ChessItem('0', position: ChessPos.fromCode(element));
- layer2.add(
- Align(
- alignment: gamer.skin.getAlign(emptyItem.position),
- child: PointComponent(size: gamer.skin.size * gamer.scale),
- ),
- );
- }
- widgets.add(
- Stack(
- alignment: Alignment.center,
- fit: StackFit.expand,
- children: layer2,
- ),
- );
- return GestureDetector(
- onTapUp: (detail) {
- if (gamer.isLock) return;
- setState(() {
- onPointer(pointTrans(detail.localPosition));
- });
- },
- child: SizedBox(
- width: gamer.skin.width,
- height: gamer.skin.height,
- child: Stack(
- alignment: Alignment.center,
- fit: StackFit.expand,
- children: widgets,
- ),
- ),
- );
- }
- }
|