Flutter国际象棋引擎插件stockfish_chess_engine的使用

Flutter国际象棋引擎插件stockfish_chess_engine的使用

使用Stockfish国际象棋引擎在您的Flutter项目中。

此项目基于Stockfish 17的源码。

使用

final stockfish = new Stockfish()

// 创建一个监听stdout的订阅:您需要在处理Stockfish之前取消此订阅。
final stockfishSubscription = stockfish.stdout.listen((message) {
    print(message);
});

// 创建一个监听stderr的订阅:您需要在处理Stockfish之前取消此订阅。
final stockfishErrorsSubscription = stockfish.stderr.listen((message) {
    print(message);
});

// 让Stockfish准备好
stockfish.stdin = 'isready'

// 向Stockfish的stdin发送命令
stockfish.stdin = 'position startpos' // 设置起始位置
stockfish.stdin = 'position fen rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2' // 设置自定义位置
stockfish.stdin = 'go movetime 1500' // 搜索最多1500毫秒的移动

// 别忘了在完成操作后释放Stockfish。
stockfishErrorsSubscription.cancel();
stockfishSubscription.cancel();
stockfish.dispose();

您可以在example文件夹中查看示例用法。

重要注意事项

  • 必须在将位置发送到stdin之前检查其有效性,否则程序会在非法位置上崩溃!为此,您可以使用chess包。
  • 由于库创建了两个隔离区,您必须在执行热重载/热重启之前释放Stockfish,并且在此之后创建一个新的Stockfish实例。
  • 如果在IPhone模拟器上进行测试,请考虑禁用Impeller(如果程序无法显示UI):flutter run --no-enable-impeller

对于Stockfish国际象棋引擎开发者

  1. 调整ffigen.yaml文件中的"llvm-path"路径(仅限Linux用户)
  2. 运行flutter pub get
  3. 取消注释src/stockfish.h文件顶部的#define _ffigen行(为了使ffi生成通过)
  4. 运行命令dart run ffigen --config ffigen.yaml。 更多信息请参阅https://pub.dev/packages/ffigen以了解不同操作系统的要求。
  5. src/stockfish.h文件中重新注释#define _ffigen行(否则Stockfish引擎编译会通过但不正确)。

更改Stockfish源文件

请参阅文件UPGRADING_STOCKFISH.md中的说明。

更改下载的NNUE文件(大或小NNUE)

  1. 前往Stockfish NNUE文件页面,从列表中选择一个参考文件。
  2. 修改CMakeLists.txt,通过替换以set (NNUE_NAME )开头的行来设置您的参考文件名,不加任何引号。
  3. 在包含#define EvalFileDefaultName的行中修改evaluate.h文件中的参考文件名(大或小),并设置您的nnue文件名,当然要带上引号。
  4. 不要忘记在重新构建之前清理项目(flutter clean然后flutter pub get)。

致谢


示例代码

import 'dart:async';

import 'package:editable_chess_board/editable_chess_board.dart';
import './edit_position_page.dart';
import './fen_validation.dart';
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:simple_chess_board/widgets/chessboard.dart';
import 'package:window_manager/window_manager.dart';

import 'package:stockfish_chess_engine/stockfish_chess_engine.dart';
import 'package:stockfish_chess_engine/stockfish_chess_engine_state.dart';

void main() {
  runApp(const MaterialApp(
    home: MyApp(),
  ));
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp> with WindowListener {
  late Stockfish _stockfish;
  String _fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
  late StreamSubscription _stockfishOutputSubsciption;
  late StreamSubscription _stockfishErrorSubsciption;
  var _timeMs = 1000.0;
  var _nextMove = '';
  var _stockfishOutputText = '';

  [@override](/user/override)
  void initState() {
    windowManager.addListener(this);
    _doStartStockfish();
    super.initState();
    windowManager.setPreventClose(true).then((_) {
      setState(() {});
    });
  }

  [@override](/user/override)
  void dispose() {
    _stopStockfish();
    super.dispose();
  }

  [@override](/user/override)
  void onWindowClose() async {
    await _stopStockfish();
    await windowManager.destroy();
  }

  void _readStockfishOutput(String output) {
    // 至少现在,Stockfish已经准备好了:更新UI。
    setState(() {
      _stockfishOutputText += "$output\n";
    });
    if (output.startsWith('bestmove')) {
      final parts = output.split(' ');
      setState(() {
        _nextMove = parts[1];
      });
    }
  }

  void _readStockfishError(String error) {
    // 至少现在,Stockfish已经准备好了:更新UI。
    setState(() {
      debugPrint("@@@$error@@@");
    });
  }

  void _editPosition(BuildContext context) async {
    final initialFen = isStrictlyValidFEN(_fen)
        ? _fen
        : 'RNBQKBNR/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
    final controller = PositionController(initialFen);
    final resultFen = await Navigator.of(context)
        .push(MaterialPageRoute<String>(builder: (context) {
      return EditPositionPage(
        positionController: controller,
      );
    }));
    if (resultFen != null) {
      setState(() {
        _fen = resultFen;
      });
      if (!isStrictlyValidFEN(_fen)) {
        if (!context.mounted) return;
        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
          content: Text('非法位置:因此未进行更改!'),
          backgroundColor: Colors.red,
        ));
        setState(() {
          _fen = initialFen;
        });
      }
    }
  }

  void _updateThinkingTime(double newValue) {
    setState(() {
      _timeMs = newValue;
    });
  }

  void _computeNextMove() {
    if (!isStrictlyValidFEN(_fen)) {
      final message = "非法位置: '$_fen' !\n";
      setState(() {
        _stockfishOutputText = message;
      });
      return;
    }
    setState(() {
      _stockfishOutputText = '';
    });
    _stockfish.stdin = 'position fen $_fen';
    _stockfish.stdin = 'go movetime ${_timeMs.toInt()}';
  }

  Future<void> _stopStockfish() async {
    if (_stockfish.state.value == StockfishState.disposed ||
        _stockfish.state.value == StockfishState.error) {
      return;
    }
    _stockfishErrorSubsciption.cancel();
    _stockfishOutputSubsciption.cancel();
    _stockfish.dispose();
    await Future.delayed(const Duration(seconds: 2));
    setState(() {});
  }

  void _doStartStockfish() async {
    _stockfish = Stockfish();
    _stockfishOutputSubsciption =
        _stockfish.stdout.listen(_readStockfishOutput);
    setState(() {
      _stockfishOutputText = '';
    });
    _stockfishErrorSubsciption = _stockfish.stderr.listen(_readStockfishError);
    await Future.delayed(const Duration(milliseconds: 1500));
    _stockfish.stdin = 'uci';
    await Future.delayed(const Duration(milliseconds: 3000));
    _stockfish.stdin = 'isready';
  }

  void _startStockfishIfNecessary() {
    setState(() {
      if (_stockfish.state.value == StockfishState.ready ||
          _stockfish.state.value == StockfishState.starting) {
        return;
      }
      _doStartStockfish();
    });
  }

  Icon _getStockfishStatusIcon() {
    Color color;
    switch (_stockfish.state.value) {
      case StockfishState.ready:
        color = Colors.green;
        break;
      case StockfishState.disposed:
      case StockfishState.error:
        color = Colors.red;
        break;
      case StockfishState.starting:
        color = Colors.orange;
    }
    return Icon(MdiIcons.circle, color: color);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Stockfish Chess Engine 示例"),
      ),
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              SizedBox(
                width: 130,
                height: 130,
                child: SimpleChessBoard(
                  engineThinking: false,
                  fen: _fen,
                  whitePlayerType: PlayerType.computer,
                  blackPlayerType: PlayerType.computer,
                  blackSideAtBottom: false,
                  cellHighlights: {},
                  chessBoardColors: ChessBoardColors(),
                  onMove: ({required move}) {},
                  onPromote: () => Future.value(null),
                  onPromotionCommited: ({required moveDone, required pieceType}) {},
                  onTap: ({required cellCoordinate}) {},
                ),
              ),
              ElevatedButton(
                onPressed: () => _editPosition(context),
                child: const Text('编辑位置'),
              ),
              Slider(
                value: _timeMs,
                onChanged: _updateThinkingTime,
                min: 500,
                max: 3000,
              ),
              Text('思考时间 : ${_timeMs.toInt()} 毫秒'),
              ElevatedButton(
                onPressed: _computeNextMove,
                child: const Text('搜索下一步'),
              ),
              Text('最佳移动: $_nextMove'),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  _getStockfishStatusIcon(),
                  ElevatedButton(
                    onPressed: _startStockfishIfNecessary,
                    child: const Text('启动Stockfish'),
                  ),
                  ElevatedButton(
                    onPressed: _stopStockfish,
                    child: const Text('停止Stockfish'),
                  ),
                ],
              ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Container(
                  width: 850.0,
                  height: 300.0,
                  decoration: BoxDecoration(
                    border: Border.all(
                      width: 2.0,
                    ),
                    borderRadius: const BorderRadius.all(
                      Radius.circular(8.0),
                    ),
                  ),
                  child: SingleChildScrollView(
                    child: Text(
                      _stockfishOutputText,
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

更多关于Flutter国际象棋引擎插件stockfish_chess_engine的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter国际象棋引擎插件stockfish_chess_engine的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


stockfish_chess_engine 是一个用于在 Flutter 应用中集成国际象棋引擎 Stockfish 的插件。Stockfish 是一个开源的国际象棋引擎,提供强大的国际象棋计算能力。通过这个插件,你可以在 Flutter 应用中实现国际象棋对弈、分析棋局等功能。

以下是使用 stockfish_chess_engine 的基本步骤:

1. 添加依赖

pubspec.yaml 文件中添加 stockfish_chess_engine 的依赖:

dependencies:
  flutter:
    sdk: flutter
  stockfish_chess_engine: ^1.0.0  # 请使用最新的版本号

然后运行 flutter pub get 以安装依赖。

2. 初始化引擎

在你的 Dart 文件中导入 stockfish_chess_engine 并初始化引擎:

import 'package:stockfish_chess_engine/stockfish_chess_engine.dart';

void initEngine() async {
  Stockfish stockfish = Stockfish();
  await stockfish.init();
}

3. 发送指令和接收响应

你可以通过 sendCommand 方法向引擎发送指令,并通过监听输出流来接收引擎的响应。

void communicateWithEngine() async {
  Stockfish stockfish = Stockfish();
  await stockfish.init();

  // 发送指令
  stockfish.sendCommand("uci");

  // 监听引擎的输出
  stockfish.outputStream.listen((output) {
    print("Engine output: $output");
  });
}

4. 设置棋局和获取最佳着法

你可以使用 position 命令设置当前的棋局,然后使用 go 命令让引擎计算最佳着法。

void getBestMove() async {
  Stockfish stockfish = Stockfish();
  await stockfish.init();

  stockfish.sendCommand("uci");
  stockfish.sendCommand("isready");

  // 设置棋局
  stockfish.sendCommand("position startpos moves e2e4 e7e5");

  // 获取最佳着法
  stockfish.sendCommand("go depth 15");

  stockfish.outputStream.listen((output) {
    print("Engine output: $output");
    if (output.startsWith("bestmove")) {
      // 解析最佳着法
      String bestMove = output.split(" ")[1];
      print("Best move: $bestMove");
    }
  });
}

5. 关闭引擎

在使用完引擎后,记得关闭它:

void closeEngine() async {
  Stockfish stockfish = Stockfish();
  await stockfish.init();

  // 关闭引擎
  await stockfish.dispose();
}

6. 处理错误

在实际使用中,可能会遇到一些错误,建议在代码中添加错误处理逻辑:

void handleErrors() async {
  Stockfish stockfish = Stockfish();
  try {
    await stockfish.init();
  } catch (e) {
    print("Error initializing engine: $e");
  }

  stockfish.outputStream.listen((output) {
    print("Engine output: $output");
  }, onError: (error) {
    print("Engine error: $error");
  });
}

7. 复杂用法

你可以结合 chess 包(package:chess/chess.dart)来处理棋局的状态和着法。chess 包提供了国际象棋的基本规则和棋局管理功能,可以与 stockfish_chess_engine 配合使用。

import 'package:chess/chess.dart';

void useChessPackage() async {
  Chess chess = Chess();
  Stockfish stockfish = Stockfish();
  await stockfish.init();

  // 执行一步着法
  chess.move("e4");

  // 将棋局发送给引擎
  stockfish.sendCommand("position fen ${chess.fen}");

  // 获取最佳着法
  stockfish.sendCommand("go depth 15");

  stockfish.outputStream.listen((output) {
    if (output.startsWith("bestmove")) {
      String bestMove = output.split(" ")[1];
      chess.move(bestMove);
      print("Updated board: ${chess.ascii}");
    }
  });
}
回到顶部