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

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

背景介绍

Stockfish for flutter 是一个用于 Flutter 应用的国际象棋引擎插件。该项目改编自 Stockfish

使用说明

命令行协议

你可以在这里找到有关 UCI 协议的描述:UCI协议

位置有效性

注意:如果你运行命令 “position fen $position”(其中 position 是 FEN 位置的值):你只能发送有效的位置!否则你的程序会崩溃!

这还意味着,如果你使用 Chess 包,你需要进行以下额外检查:

  1. 每个玩家必须只有一个国王。
  2. 等级 1 和等级 8 上不能有任何兵。

否则,你可以使用来自 its repository 的最新版本的 chess,通过在 pubspec.yaml 中添加以下内容:

chess:
    git:
      url: https://github.com/davecom/chess.dart
      ref: 9bc48075ecaab3eb3356c26fee4404a362fe5d54

iOS 项目要求

iOS 项目必须设置 `IPHONEOS_DEPLOYMENT_TARGET >=11.0。

示例代码

添加依赖

更新 pubspec.yaml 文件中的 dependencies 部分:

dependencies:
  stockfish_for_flutter: ^0.1.0

初始化引擎

创建一个新的实例并监听状态变化:

import 'package:stockfish_for_flutter/stockfish.dart';

// 创建一个新的实例
final stockfish = Stockfish();

// state 是一个 ValueListenable<StockfishState>
print(stockfish.state.value); // StockfishState.starting

// 引擎需要一些时间来启动
await Future.delayed(Duration(seconds: 1));
print(stockfish.state.value); // StockfishState.ready

发送UCI命令

等待状态变为就绪后,发送命令:

stockfish.stdin = 'isready';
stockfish.stdin = 'go movetime 3000';
stockfish.stdin = 'go infinite';
stockfish.stdin = 'stop';

引擎输出将被定向到一个 Stream<String>,可以添加监听器来处理结果:

stockfish.stdout.listen((line) {
  // 处理有用的信息
  print(line);
});

销毁/热重载

当 Stockfish 引擎正在运行时,有两个活动的隔离区,这会干扰 Flutter 的热重载功能,因此在尝试重新加载之前需要销毁它:

// 发送UCI quit命令
stockfish.stdin = 'quit';

// 或者更简单的方式...
stockfish.dispose();

注意:一次只能创建一个实例。如果已经存在一个活动的实例,Stockfish() 工厂方法将返回 null

生成Stockfish绑定

  1. 运行 flutter pub get
  2. 取消注释 src/stockfish_for_flutter.h 文件顶部的 #define _ffigen 行(以便ffi生成可以通过)。
  3. 运行命令 flutter pub run ffigen --config ffigen.yaml
  4. src/stockfish_for_flutter.h 文件中重新注释 #define _ffigen 行(否则 Stockfish 引擎编译会通过但不正确)。

升级

更改下载的NNUE文件

  1. 访问 Stockfish NNUE文件页面 并选择列表中的一个参考。
  2. 修改 android/CMakeLists.txt,通过替换以 set (NNUE_NAME ) 开头的行来设置你的参考名称,不要加引号。
  3. 修改 ios/stockfish.podspec,将两个引用到 nn-XXXXX.nnue(其中 XXXX 是“序列”值)替换为要下载的NNUE文件名。
  4. 修改 evaluate.h 文件中的 #define EvalFileDefaultName 行,通过设置你的NNUE文件名(当然要用引号)。

更新Stockfish版本

只需将 /src/stockfish 文件夹替换为新版本的源码,并调整上述提到的NNUE文件引用即可。

如果有必要,将 main() 函数中的代码导入到 stockfish 源文件的 cpp/bridge/stockfish.cpp 中的 main() 函数。

完整示例代码

import 'dart:async';

import 'package:clipboard/clipboard.dart';
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:chess/chess.dart' as chess_lib;
import 'package:window_manager/window_manager.dart';
import 'package:logger/logger.dart';

import 'package:stockfish_for_flutter/stockfish.dart';

class MyLogFilter extends LogFilter {
  [@override](/user/override)
  bool shouldLog(LogEvent event) {
    return true;
  }
}

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

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

class MyAppState extends State<MyApp> with WindowListener {
  late Stockfish _stockfish;
  final _fenController = TextEditingController(
      text: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
  late StreamSubscription _stockfishOutputSubsciption;
  var _timeMs = 1000.0;
  var _nextMove = '';
  var _stockfishOutputText = '';
  final _logger = Logger(filter: MyLogFilter());

  [@override](/user/override)
  void initState() {
    windowManager.addListener(this);
    _overrideDefaultCloseHandler();
    _doStartStockfish();
    super.initState();
  }

  Future<void> _overrideDefaultCloseHandler() async {
    await windowManager.setPreventClose(true);
    setState(() {});
  }

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

  [@override](/user/override)
  void onWindowClose() async {
    _stopStockfish();
    await Future.delayed(const Duration(milliseconds: 200));
    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 _pasteFen() {
    FlutterClipboard.paste().then((value) {
      setState(() {
        _fenController.text = value;
      });
    });
  }

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

  bool _validPosition() {
    final chess = chess_lib.Chess();
    return chess.load(_fenController.text.trim());
  }

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

  void _stopStockfish() async {
    if (_stockfish.state.value == StockfishState.disposed ||
        _stockfish.state.value == StockfishState.error) {
      return;
    }
    _stockfishOutputSubsciption.cancel();
    _stockfish.stdin = 'quit';
    await Future.delayed(const Duration(milliseconds: 200));
    setState(() {});
  }

  void _doStartStockfish() async {
    _stockfish = Stockfish();
    _stockfishOutputSubsciption =
        _stockfish.stdout.listen(_readStockfishOutput);
    setState(() {
      _stockfishOutputText = '';
    });
    await Future.delayed(const Duration(milliseconds: 1100));
    _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 SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: const Text("Stockfish Chess Engine example"),
        ),
        body: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                TextField(
                  controller: _fenController,
                  decoration: const InputDecoration(
                    hintText: 'Position FEN value',
                    border: OutlineInputBorder(),
                  ),
                ),
                ElevatedButton(
                  onPressed: _pasteFen,
                  child: const Text('粘贴FEN'),
                ),
                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_for_flutter的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

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


当然,下面是一个关于如何在Flutter应用中使用stockfish_for_flutter插件来实现国际象棋引擎功能的示例代码。stockfish_for_flutter是一个Flutter插件,它封装了Stockfish国际象棋引擎,允许你在Flutter应用中执行棋局分析和生成移动等操作。

首先,确保你的Flutter项目已经添加了stockfish_for_flutter依赖。在你的pubspec.yaml文件中添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  stockfish_for_flutter: ^最新版本号  # 请替换为当前最新版本号

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

接下来是一个简单的Flutter应用示例,展示如何使用stockfish_for_flutter插件:

import 'package:flutter/material.dart';
import 'package:stockfish_for_flutter/stockfish_for_flutter.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Stockfish for Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Stockfish? stockfish;
  String result = "";

  @override
  void initState() {
    super.initState();
    initStockfish();
  }

  Future<void> initStockfish() async {
    stockfish = Stockfish();
    await stockfish!.init();
  }

  Future<void> startGame() async {
    // 设置初始局面(FEN表示法)
    String fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
    await stockfish!.setFen(fen);

    // 获取最佳移动
    String? bestMove = await stockfish!.getBestMove();
    setState(() {
      result = "Best Move: $bestMove";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stockfish for Flutter Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              result,
              style: TextStyle(fontSize: 24),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: startGame,
              child: Text('Start Game'),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    stockfish?.dispose();
    super.dispose();
  }
}

在这个示例中,我们做了以下几件事:

  1. 添加依赖:在pubspec.yaml文件中添加了stockfish_for_flutter依赖。
  2. 初始化Stockfish引擎:在initState方法中初始化Stockfish引擎。
  3. 设置初始局面:使用FEN(Forsyth-Edwards Notation)表示法设置初始局面。
  4. 获取最佳移动:调用getBestMove方法获取当前局面的最佳移动。
  5. 显示结果:将获取到的最佳移动显示在界面上。
  6. 资源释放:在dispose方法中释放Stockfish引擎资源。

请注意,这个示例代码是一个简单的演示,实际项目中你可能需要处理更多的逻辑,比如用户输入、UI更新、错误处理等。此外,Stockfish引擎可能需要一些时间来进行计算,特别是在处理复杂局面时,因此在实际应用中你可能需要考虑添加加载指示器或处理异步操作的用户体验。

回到顶部