flutter: Refactor a lot
This commit is contained in:
parent
beee9a0cef
commit
696095724a
|
@ -20,7 +20,6 @@
|
|||
abs(value) => value > 0 ? value : -value;
|
||||
|
||||
int binarySearch(List<int> array, int start, int end, int key) {
|
||||
//
|
||||
if (start > end) return -1;
|
||||
|
||||
if (array[start] == key) return start;
|
|
@ -20,29 +20,26 @@
|
|||
import 'profile.dart';
|
||||
|
||||
class Config {
|
||||
//
|
||||
static bool bgmEnabled = false;
|
||||
static bool toneEnabled = true;
|
||||
static int stepTime = 5000;
|
||||
static int thinkingTime = 5000;
|
||||
|
||||
static Future<void> loadProfile() async {
|
||||
//
|
||||
final profile = await Profile.shared();
|
||||
|
||||
Config.bgmEnabled = profile['bgm-enabled'] ?? false;
|
||||
Config.toneEnabled = profile['tone-enabled'] ?? true;
|
||||
Config.stepTime = profile['step-time'] ?? 5000;
|
||||
Config.thinkingTime = profile['thinking-time'] ?? 5000;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Future<bool> save() async {
|
||||
//
|
||||
final profile = await Profile.shared();
|
||||
|
||||
profile['bgm-enabled'] = Config.bgmEnabled;
|
||||
profile['tone-enabled'] = Config.toneEnabled;
|
||||
profile['step-time'] = Config.stepTime;
|
||||
profile['thinking-time'] = Config.thinkingTime;
|
||||
|
||||
profile.commit();
|
||||
|
||||
|
|
|
@ -23,18 +23,16 @@ import 'dart:io';
|
|||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class Profile {
|
||||
//
|
||||
static const DefaultFileName = 'default-profile.json';
|
||||
static const defaultFileName = 'default-profile.json';
|
||||
static Profile _shared;
|
||||
|
||||
File _file;
|
||||
Map<String, dynamic> _values = {};
|
||||
|
||||
static shared() async {
|
||||
//
|
||||
if (_shared == null) {
|
||||
_shared = Profile();
|
||||
await _shared._load(DefaultFileName);
|
||||
await _shared._load(defaultFileName);
|
||||
}
|
||||
|
||||
return _shared;
|
||||
|
@ -45,7 +43,6 @@ class Profile {
|
|||
operator []=(String key, dynamic value) => _values[key] = value;
|
||||
|
||||
Future<bool> commit() async {
|
||||
//
|
||||
_file.create(recursive: true);
|
||||
|
||||
try {
|
||||
|
@ -60,7 +57,6 @@ class Profile {
|
|||
}
|
||||
|
||||
Future<bool> _load(String fileName) async {
|
||||
//
|
||||
final docDir = await getApplicationDocumentsDirectory();
|
||||
_file = File('${docDir.path}/$fileName');
|
||||
|
||||
|
|
|
@ -17,41 +17,37 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class AnalysisItem {
|
||||
//
|
||||
String move, stepName;
|
||||
class AnalyzeItem {
|
||||
String move, moveName;
|
||||
int score;
|
||||
double winrate;
|
||||
double winRate;
|
||||
|
||||
AnalysisItem({this.move, this.score, this.winrate});
|
||||
AnalyzeItem({this.move, this.score, this.winRate});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '{move: ${stepName ?? move}, score: $score, winrate: $winrate}';
|
||||
return '{move: ${moveName ?? move}, score: $score, winRate: $winRate}';
|
||||
}
|
||||
}
|
||||
|
||||
class AnalysisFetcher {
|
||||
//
|
||||
static List<AnalysisItem> fetch(String response, {limit = 5}) {
|
||||
//
|
||||
class AnalyzeFetcher {
|
||||
static List<AnalyzeItem> fetch(String response, {limit = 5}) {
|
||||
final segments = response.split('|');
|
||||
|
||||
List<AnalysisItem> result = [];
|
||||
List<AnalyzeItem> result = [];
|
||||
|
||||
final regx = RegExp(r'move:(.{4}).+score:(\-?\d+).+winrate:(\d+.?\d*)');
|
||||
final regExp = RegExp(r'move:(.{4}).+score:(\-?\d+).+winRate:(\d+.?\d*)');
|
||||
|
||||
for (var segment in segments) {
|
||||
//
|
||||
final match = regx.firstMatch(segment);
|
||||
final match = regExp.firstMatch(segment);
|
||||
|
||||
if (match == null) break;
|
||||
|
||||
final move = match.group(1);
|
||||
final score = int.parse(match.group(2));
|
||||
final winrate = double.parse(match.group(3));
|
||||
final winRate = double.parse(match.group(3));
|
||||
|
||||
result.add(AnalysisItem(move: move, score: score, winrate: winrate));
|
||||
result.add(AnalyzeItem(move: move, score: score, winRate: winRate));
|
||||
if (result.length == limit) break;
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import '../mill/position.dart';
|
||||
import 'package:sanmill/mill/position.dart';
|
||||
|
||||
enum EngineType { Cloud, Native }
|
||||
|
||||
|
@ -28,10 +28,7 @@ class EngineResponse {
|
|||
}
|
||||
|
||||
abstract class AiEngine {
|
||||
//
|
||||
Future<void> startup() async {}
|
||||
|
||||
Future<void> shutdown() async {}
|
||||
|
||||
Future<EngineResponse> search(Position position, {bool byUser = true});
|
||||
}
|
||||
|
|
|
@ -18,30 +18,25 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:sanmill/mill/mill.dart';
|
||||
import 'package:sanmill/mill/position.dart';
|
||||
|
||||
import '../mill/mill.dart';
|
||||
import '../mill/position.dart';
|
||||
import 'engine.dart';
|
||||
|
||||
class NativeEngine extends AiEngine {
|
||||
//
|
||||
static const platform = const MethodChannel('com.calcitem.sanmill/engine');
|
||||
|
||||
Future<void> startup() async {
|
||||
//
|
||||
try {
|
||||
await platform.invokeMethod('startup');
|
||||
} catch (e) {
|
||||
print('Native startup Error: $e');
|
||||
}
|
||||
|
||||
//await setBookFile();
|
||||
|
||||
await waitResponse(['uciok'], sleep: 1, times: 30);
|
||||
}
|
||||
|
||||
Future<void> send(String command) async {
|
||||
//
|
||||
try {
|
||||
print("send: $command");
|
||||
await platform.invokeMethod('send', command);
|
||||
|
@ -51,7 +46,6 @@ class NativeEngine extends AiEngine {
|
|||
}
|
||||
|
||||
Future<String> read() async {
|
||||
//
|
||||
try {
|
||||
return await platform.invokeMethod('read');
|
||||
} catch (e) {
|
||||
|
@ -62,7 +56,6 @@ class NativeEngine extends AiEngine {
|
|||
}
|
||||
|
||||
Future<void> shutdown() async {
|
||||
//
|
||||
try {
|
||||
await platform.invokeMethod('shutdown');
|
||||
} catch (e) {
|
||||
|
@ -71,7 +64,6 @@ class NativeEngine extends AiEngine {
|
|||
}
|
||||
|
||||
Future<bool> isReady() async {
|
||||
//
|
||||
try {
|
||||
return await platform.invokeMethod('isReady');
|
||||
} catch (e) {
|
||||
|
@ -82,7 +74,6 @@ class NativeEngine extends AiEngine {
|
|||
}
|
||||
|
||||
Future<bool> isThinking() async {
|
||||
//
|
||||
try {
|
||||
return await platform.invokeMethod('isThinking');
|
||||
} catch (e) {
|
||||
|
@ -92,32 +83,11 @@ class NativeEngine extends AiEngine {
|
|||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
Future setBookFile() async {
|
||||
//
|
||||
final docDir = await getApplicationDocumentsDirectory();
|
||||
final bookFile = File('${docDir.path}/book.dat');
|
||||
|
||||
try {
|
||||
if (!await bookFile.exists()) {
|
||||
await bookFile.create(recursive: true);
|
||||
final bytes = await rootBundle.load("assets/book.dat");
|
||||
await bookFile.writeAsBytes(bytes.buffer.asUint8List());
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
await send("setoption bookfiles ${bookFile.path}");
|
||||
}
|
||||
*/
|
||||
|
||||
@override
|
||||
Future<EngineResponse> search(Position position, {bool byUser = true}) async {
|
||||
//
|
||||
if (await isThinking()) await stopSearching();
|
||||
|
||||
send(buildPositionCommand(position));
|
||||
send(getPositionFen(position));
|
||||
send('go');
|
||||
|
||||
final response = await waitResponse(['bestmove', 'nobestmove']);
|
||||
|
@ -125,13 +95,12 @@ class NativeEngine extends AiEngine {
|
|||
print("response: $response");
|
||||
|
||||
if (response.startsWith('bestmove')) {
|
||||
//
|
||||
var step = response.substring('bestmove'.length + 1);
|
||||
var best = response.substring('bestmove'.length + 1);
|
||||
|
||||
final pos = step.indexOf(' ');
|
||||
if (pos > -1) step = step.substring(0, pos);
|
||||
final pos = best.indexOf(' ');
|
||||
if (pos > -1) best = best.substring(0, pos);
|
||||
|
||||
return EngineResponse('move', value: Move.fromEngineMove(step));
|
||||
return EngineResponse('move', value: Move.set(best));
|
||||
}
|
||||
|
||||
if (response.startsWith('nobestmove')) {
|
||||
|
@ -143,7 +112,6 @@ class NativeEngine extends AiEngine {
|
|||
|
||||
Future<String> waitResponse(List<String> prefixes,
|
||||
{sleep = 100, times = 100}) async {
|
||||
//
|
||||
if (times <= 0) return '';
|
||||
|
||||
final response = await read();
|
||||
|
@ -164,7 +132,7 @@ class NativeEngine extends AiEngine {
|
|||
await send('stop');
|
||||
}
|
||||
|
||||
String buildPositionCommand(Position position) {
|
||||
String getPositionFen(Position position) {
|
||||
/*
|
||||
final startPosition = position.lastCapturedPosition;
|
||||
final moves = position.movesSinceLastCaptured();
|
||||
|
|
|
@ -22,9 +22,9 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import './routes/main_menu.dart';
|
||||
import 'services/audios.dart';
|
||||
import 'services/player.dart';
|
||||
import 'widgets/main_menu.dart';
|
||||
|
||||
void main() {
|
||||
//
|
||||
|
|
|
@ -17,14 +17,13 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:sanmill/common/types.dart';
|
||||
import 'package:sanmill/mill/types.dart';
|
||||
|
||||
import '../mill/mill.dart';
|
||||
import '../mill/position.dart';
|
||||
import 'mill.dart';
|
||||
import 'position.dart';
|
||||
|
||||
class Battle {
|
||||
//
|
||||
static Battle _instance;
|
||||
class Game {
|
||||
static Game _instance;
|
||||
|
||||
Position _position;
|
||||
int _focusIndex, _blurIndex;
|
||||
|
@ -32,21 +31,16 @@ class Battle {
|
|||
String sideToMove = Color.black;
|
||||
|
||||
// 是否黑白反转
|
||||
bool isInverted;
|
||||
bool isColorInverted;
|
||||
|
||||
Map<String, bool> isAiPlayer = {Color.black: false, Color.white: true};
|
||||
Map<String, bool> isAiSearching = {Color.black: false, Color.white: false};
|
||||
|
||||
Battle() {
|
||||
//cmdlist = new List;
|
||||
}
|
||||
Map<String, bool> isAi = {Color.black: false, Color.white: true};
|
||||
Map<String, bool> isSearching = {Color.black: false, Color.white: false};
|
||||
|
||||
bool aiIsSearching() {
|
||||
return isAiSearching[Color.black] == true ||
|
||||
isAiSearching[Color.white] == true;
|
||||
return isSearching[Color.black] == true || isSearching[Color.white] == true;
|
||||
}
|
||||
|
||||
void gameStart() {
|
||||
void start() {
|
||||
position.start();
|
||||
}
|
||||
|
||||
|
@ -60,7 +54,7 @@ class Battle {
|
|||
static bool hasSound = true;
|
||||
|
||||
// 是否必败时认输
|
||||
bool resignIfMostLose_ = false;
|
||||
bool resignIfMostLose = false;
|
||||
|
||||
// 是否自动交换先后手
|
||||
bool isAutoChangeFirstMove = false;
|
||||
|
@ -74,34 +68,34 @@ class Battle {
|
|||
// 提示语
|
||||
String tips;
|
||||
|
||||
List<String> cmdlist = [""];
|
||||
List<String> moveHistory = [""];
|
||||
|
||||
String getTips() => tips;
|
||||
|
||||
bool isAIsTurn() {
|
||||
return isAiPlayer[sideToMove];
|
||||
bool isAiToMove() {
|
||||
return isAi[sideToMove];
|
||||
}
|
||||
|
||||
static get shared {
|
||||
_instance ??= Battle();
|
||||
_instance ??= Game();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
init() {
|
||||
_position = Position();
|
||||
_focusIndex = _blurIndex = Move.invalidValue;
|
||||
_focusIndex = _blurIndex = Move.invalidMove;
|
||||
}
|
||||
|
||||
newGame() {
|
||||
Battle.shared.position.init();
|
||||
_focusIndex = _blurIndex = Move.invalidValue;
|
||||
cmdlist = [""];
|
||||
Game.shared.position.init();
|
||||
_focusIndex = _blurIndex = Move.invalidMove;
|
||||
moveHistory = [""];
|
||||
sideToMove = Color.black;
|
||||
}
|
||||
|
||||
select(int pos) {
|
||||
_focusIndex = pos;
|
||||
_blurIndex = Move.invalidValue;
|
||||
_blurIndex = Move.invalidMove;
|
||||
//Audios.playTone('click.mp3');
|
||||
}
|
||||
|
||||
|
@ -112,30 +106,23 @@ class Battle {
|
|||
_blurIndex = from;
|
||||
_focusIndex = to;
|
||||
|
||||
/*
|
||||
if (ChessRules.checked(position)) {
|
||||
//Audios.playTone('check.mp3');
|
||||
} else {
|
||||
//Audios.playTone(captured != Piece.Empty ? 'capture.mp3' : 'move.mp3');
|
||||
}
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool regret({steps = 2}) {
|
||||
bool regret({moves = 2}) {
|
||||
//
|
||||
// 轮到自己走棋的时候,才能悔棋
|
||||
// TODO
|
||||
if (_position.side != Color.white) {
|
||||
//Audios.playTone('invalid.mp3');
|
||||
return false;
|
||||
}
|
||||
|
||||
var regreted = false;
|
||||
var regretted = false;
|
||||
|
||||
/// 悔棋一回合(两步),才能撤回自己上一次的动棋
|
||||
|
||||
for (var i = 0; i < steps; i++) {
|
||||
for (var i = 0; i < moves; i++) {
|
||||
//
|
||||
if (!_position.regret()) break;
|
||||
|
||||
|
@ -148,13 +135,13 @@ class Battle {
|
|||
//
|
||||
} else {
|
||||
//
|
||||
_blurIndex = _focusIndex = Move.invalidValue;
|
||||
_blurIndex = _focusIndex = Move.invalidMove;
|
||||
}
|
||||
|
||||
regreted = true;
|
||||
regretted = true;
|
||||
}
|
||||
|
||||
if (regreted) {
|
||||
if (regretted) {
|
||||
//Audios.playTone('regret.mp3');
|
||||
return true;
|
||||
}
|
||||
|
@ -164,7 +151,7 @@ class Battle {
|
|||
}
|
||||
|
||||
clear() {
|
||||
_blurIndex = _focusIndex = Move.invalidValue;
|
||||
_blurIndex = _focusIndex = Move.invalidMove;
|
||||
}
|
||||
|
||||
GameResult scanBattleResult() {
|
||||
|
@ -196,12 +183,12 @@ class Battle {
|
|||
|
||||
// 如果未开局则开局
|
||||
if (position.phase == Phase.ready) {
|
||||
gameStart();
|
||||
start();
|
||||
}
|
||||
|
||||
print("Computer: $cmd");
|
||||
|
||||
cmdlist.add(cmd);
|
||||
moveHistory.add(cmd);
|
||||
|
||||
if (!position.command(cmd)) {
|
||||
return false;
|
||||
|
@ -209,30 +196,6 @@ class Battle {
|
|||
|
||||
sideToMove = position.sideToMove();
|
||||
|
||||
String winner = position.winner;
|
||||
|
||||
if (winner != Color.nobody) {
|
||||
//resumeAiThreads(position.sideToMove());
|
||||
// TODO
|
||||
} else {
|
||||
// pauseThreads();
|
||||
|
||||
/*
|
||||
if (gameOptions.getAutoRestart()) {
|
||||
saveScore();
|
||||
|
||||
gameReset();
|
||||
gameStart();
|
||||
|
||||
if (isAiPlayer[BLACK]) {
|
||||
setEngine(BLACK, true);
|
||||
}
|
||||
if (isAiPlayer[WHITE]) {
|
||||
setEngine(WHITE, true);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
total = position.score[Color.black] +
|
||||
position.score[Color.white] +
|
||||
position.score[Color.draw];
|
||||
|
@ -247,7 +210,7 @@ class Battle {
|
|||
drawRate = position.score[Color.draw] * 100 / total;
|
||||
}
|
||||
|
||||
String outStr = "Score: " +
|
||||
String stat = "Score: " +
|
||||
position.score[Color.black].toString() +
|
||||
" : " +
|
||||
position.score[Color.white].toString() +
|
||||
|
@ -264,7 +227,7 @@ class Battle {
|
|||
"%" +
|
||||
"\n";
|
||||
|
||||
print(outStr);
|
||||
print(stat);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:sanmill/common/types.dart';
|
||||
import 'types.dart';
|
||||
|
||||
Map<int, int> squareToIndex = {
|
||||
8: 17,
|
||||
|
@ -56,7 +56,6 @@ int makeSquare(int file, int rank) {
|
|||
enum GameResult { pending, win, lose, draw }
|
||||
|
||||
class Color {
|
||||
//
|
||||
static const none = '*';
|
||||
static const black = '@';
|
||||
static const white = 'O';
|
||||
|
@ -85,24 +84,19 @@ class Color {
|
|||
}
|
||||
|
||||
class Piece {
|
||||
//
|
||||
static const noPiece = '*';
|
||||
//
|
||||
static const blackStone = '@';
|
||||
static const whiteStone = 'O';
|
||||
static const ban = 'X';
|
||||
static const noPiece = Color.none;
|
||||
static const blackStone = Color.black;
|
||||
static const whiteStone = Color.white;
|
||||
static const ban = Color.ban;
|
||||
|
||||
static bool isBlack(String c) => '@'.contains(c);
|
||||
|
||||
static bool isWhite(String c) => 'O'.contains(c);
|
||||
|
||||
static bool isBan(String c) => 'X'.contains(c);
|
||||
|
||||
static bool isEmpty(String c) => '*'.contains(c);
|
||||
static bool isEmpty(String c) => noPiece.contains(c);
|
||||
static bool isBlack(String c) => blackStone.contains(c);
|
||||
static bool isWhite(String c) => whiteStone.contains(c);
|
||||
static bool isBan(String c) => ban.contains(c);
|
||||
}
|
||||
|
||||
class Move {
|
||||
static const invalidValue = -1;
|
||||
static const invalidMove = -1;
|
||||
|
||||
// Square
|
||||
int from = 0;
|
||||
|
@ -118,7 +112,7 @@ class Move {
|
|||
int fromIndex = 0;
|
||||
int toIndex = 0;
|
||||
|
||||
String captured;
|
||||
String removed;
|
||||
|
||||
// 'move' is the UCI engine's move-string
|
||||
String move;
|
||||
|
@ -129,13 +123,13 @@ class Move {
|
|||
String counterMarks;
|
||||
|
||||
parse() {
|
||||
if (!validateEngineMove(move)) {
|
||||
if (!legal(move)) {
|
||||
throw "Error: Invalid Move: $move";
|
||||
}
|
||||
|
||||
if (move[0] == '-' && move.length == "-(1,2)".length) {
|
||||
type = MoveType.remove;
|
||||
from = fromFile = fromRank = fromIndex = invalidValue;
|
||||
from = fromFile = fromRank = fromIndex = invalidMove;
|
||||
toFile = int.parse(move[2]);
|
||||
toRank = int.parse(move[4]);
|
||||
//captured = Piece.noPiece;
|
||||
|
@ -147,13 +141,13 @@ class Move {
|
|||
fromIndex = squareToIndex[from];
|
||||
toFile = int.parse(move[8]);
|
||||
toRank = int.parse(move[10]);
|
||||
captured = Piece.noPiece;
|
||||
removed = Piece.noPiece;
|
||||
} else if (move.length == "(1,2)".length) {
|
||||
type = MoveType.place;
|
||||
from = fromFile = fromRank = fromIndex = invalidValue;
|
||||
from = fromFile = fromRank = fromIndex = invalidMove;
|
||||
toFile = int.parse(move[1]);
|
||||
toRank = int.parse(move[3]);
|
||||
captured = Piece.noPiece;
|
||||
removed = Piece.noPiece;
|
||||
} else if (move == "draw") {
|
||||
// TODO
|
||||
print("Computer request draw");
|
||||
|
@ -169,44 +163,24 @@ class Move {
|
|||
parse();
|
||||
}
|
||||
|
||||
/*
|
||||
Move(this.from, this.to,
|
||||
{this.captured = Piece.noPiece, this.counterMarks = '0 0'}) {
|
||||
//
|
||||
fx = from % 9;
|
||||
fy = from ~/ 9;
|
||||
|
||||
tx = to % 9;
|
||||
ty = to ~/ 9;
|
||||
|
||||
if (fx < 0 || fx > 8 || fy < 0 || fy > 9) {
|
||||
throw "Error: Invalid Step (from:$from, to:$to)";
|
||||
}
|
||||
|
||||
move = String.fromCharCode('a'.codeUnitAt(0) + fx) + (9 - fy).toString();
|
||||
move += String.fromCharCode('a'.codeUnitAt(0) + tx) + (9 - ty).toString();
|
||||
}
|
||||
*/
|
||||
|
||||
/// 引擎返回的招法用是如此表示的,例如:
|
||||
/// 落子:(1,2)
|
||||
/// 吃子:-(1,2)
|
||||
/// 走子:(3,1)->(2,1)
|
||||
|
||||
Move.fromEngineMove(String move) {
|
||||
//
|
||||
Move.set(String move) {
|
||||
this.move = move;
|
||||
parse();
|
||||
}
|
||||
|
||||
static bool validateEngineMove(String move) {
|
||||
static bool legal(String move) {
|
||||
if (move == "draw") {
|
||||
return true; // TODO
|
||||
}
|
||||
|
||||
if (move == null || move.length > "(3,1)->(2,1)".length) return false;
|
||||
|
||||
String sets = "0123456789(,)->";
|
||||
String range = "0123456789(,)->";
|
||||
|
||||
if (!(move[0] == '(' || move[0] == '-')) {
|
||||
return false;
|
||||
|
@ -217,7 +191,13 @@ class Move {
|
|||
}
|
||||
|
||||
for (int i = 0; i < move.length; i++) {
|
||||
if (!sets.contains(move[i])) return false;
|
||||
if (!range.contains(move[i])) return false;
|
||||
}
|
||||
|
||||
if (move.length == "(3,1)->(2,1)".length) {
|
||||
if (move.substring(0, 4) == move.substring(7, 11)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:sanmill/mill/mill.dart';
|
||||
import 'package:sanmill/mill/recorder.dart';
|
||||
import 'package:sanmill/mill/rule.dart';
|
||||
|
||||
import '../common/types.dart';
|
||||
import '../mill/mill.dart';
|
||||
import '../mill/recorder.dart';
|
||||
import '../mill/rule.dart';
|
||||
import 'types.dart';
|
||||
|
||||
class StateInfo {
|
||||
/*
|
||||
|
@ -44,10 +44,10 @@ class Position {
|
|||
|
||||
GameResult result = GameResult.pending;
|
||||
|
||||
List<String> board = List<String>(40);
|
||||
List<String> _grid = List<String>(49); // 7 * 7
|
||||
List<String> board = List<String>(sqNumber);
|
||||
List<String> _grid = List<String>(7 * 7);
|
||||
|
||||
MillRecorder _recorder;
|
||||
GameRecorder _recorder;
|
||||
|
||||
Map<String, int> pieceCountInHand = {Color.black: 12, Color.white: 12};
|
||||
Map<String, int> pieceCountOnBoard = {Color.black: 0, Color.white: 0};
|
||||
|
@ -64,6 +64,7 @@ class Position {
|
|||
String us = Color.black;
|
||||
String them = Color.white;
|
||||
String winner = Color.nobody;
|
||||
|
||||
GameOverReason gameOverReason = GameOverReason.noReason;
|
||||
|
||||
Phase phase = Phase.none;
|
||||
|
@ -145,57 +146,9 @@ class Position {
|
|||
return pieceOn(fromSq(move));
|
||||
}
|
||||
|
||||
bool selectPieceFR(int file, int rank) {
|
||||
return selectPieceSQ(makeSquare(file, rank));
|
||||
}
|
||||
|
||||
bool putPieceFR(int file, int rank) {
|
||||
bool ret = putPieceSQ(makeSquare(file, rank));
|
||||
|
||||
if (ret) {
|
||||
updateScore();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool movePieceFR(int file1, int rank1, int file2, int rank2) {
|
||||
return movePieceSQ(makeSquare(file1, rank1), makeSquare(file2, rank2));
|
||||
}
|
||||
|
||||
bool removePieceFR(int file, int rank) {
|
||||
bool ret = removePieceSQ(makeSquare(file, rank));
|
||||
|
||||
if (ret) {
|
||||
updateScore();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool selectPieceSQ(int sq) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
bool putPieceSQ(int sq) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
bool movePieceSQ(int fromSq, int toSq) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
bool removePieceSQ(int sq) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
bool movePiece(int fromSq, int toSq) {
|
||||
if (selectPiece(fromSq)) {
|
||||
return putPiece(toSq);
|
||||
bool movePiece(int from, int to) {
|
||||
if (selectPiece(from)) {
|
||||
return putPiece(to);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -219,7 +172,7 @@ class Position {
|
|||
|
||||
// TODO
|
||||
|
||||
_recorder = MillRecorder(lastCapturedPosition: fen());
|
||||
_recorder = GameRecorder(lastPositionWithRemove: fen());
|
||||
}
|
||||
|
||||
Position() {
|
||||
|
@ -227,41 +180,6 @@ class Position {
|
|||
init();
|
||||
}
|
||||
|
||||
void set(String fenStr) {
|
||||
/*
|
||||
A FEN string defines a particular position using only the ASCII character set.
|
||||
|
||||
A FEN string contains six fields separated by a space. The fields are:
|
||||
|
||||
1) Piece placement. Each rank is described, starting
|
||||
with rank 1 and ending with rank 8. Within each rank, the contents of each
|
||||
square are described from file A through file C. Following the Standard
|
||||
Algebraic Notation (SAN), each piece is identified by a single letter taken
|
||||
from the standard English names. White pieces are designated using "O"
|
||||
whilst Black uses "@". Blank uses "*". Banned uses "X".
|
||||
noted using digits 1 through 8 (the number of blank squares), and "/"
|
||||
separates ranks.
|
||||
|
||||
2) Active color. "w" means white moves next, "b" means black.
|
||||
|
||||
3) Phrase.
|
||||
|
||||
4) Action.
|
||||
|
||||
5) Black on board/Black in hand/White on board/White in hand/need to remove
|
||||
|
||||
6) Halfmove clock. This is the number of halfmoves since the last
|
||||
capture. This is used to determine if a draw can be claimed under the
|
||||
fifty-move rule.
|
||||
|
||||
7) Fullmove number. The number of the full move. It starts at 1, and is
|
||||
incremented after Black's move.
|
||||
*/
|
||||
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
/// fen() returns a FEN representation of the position.
|
||||
|
||||
String fen() {
|
||||
|
@ -354,19 +272,19 @@ class Position {
|
|||
|
||||
/// Position::legal() tests whether a pseudo-legal move is legal
|
||||
|
||||
bool legal(int move) {
|
||||
assert(isOk(move));
|
||||
bool legal(Move move) {
|
||||
if (!isOk(move.from) || !isOk(move.to)) return false;
|
||||
|
||||
String us = _sideToMove;
|
||||
int fromSQ = fromSq(move);
|
||||
int toSQ = toSq(move);
|
||||
|
||||
if (fromSQ == toSQ) {
|
||||
if (move.from == move.to) {
|
||||
print("Move $move.move from == to");
|
||||
return false; // TODO: Same with is_ok(m)
|
||||
}
|
||||
|
||||
if (phase == Phase.moving && typeOf(move) != MoveType.remove) {
|
||||
if (movedPiece(move) != us) {
|
||||
if (move.type == MoveType.remove) {
|
||||
if (movedPiece(move.to) != us) {
|
||||
print("Move $move.to to != us");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -376,20 +294,10 @@ class Position {
|
|||
return true;
|
||||
}
|
||||
|
||||
/// Position::pseudo_legal() takes a random move and tests whether the move is
|
||||
/// pseudo legal. It is used to validate moves from TT that can be corrupted
|
||||
/// due to SMP concurrent access or hash position key aliasing.
|
||||
|
||||
bool pseudoLegal(int move) {
|
||||
// TODO
|
||||
return legal(move);
|
||||
}
|
||||
|
||||
void doMove(Move m) {
|
||||
//
|
||||
//if (!validateMove(m)) return null;
|
||||
|
||||
//final move = Move(m);
|
||||
if (!legal(m)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
|
||||
|
@ -421,22 +329,11 @@ class Position {
|
|||
|
||||
this.move = m;
|
||||
|
||||
//StepName.translate(this, move);
|
||||
_recorder.stepIn(move, this);
|
||||
|
||||
// 交换走棋方
|
||||
//_sideToMove = Color.opponent(_sideToMove);
|
||||
}
|
||||
|
||||
void doNullMove() {
|
||||
changeSideToMove();
|
||||
}
|
||||
|
||||
void undoNullMove() {
|
||||
changeSideToMove();
|
||||
_recorder.moveIn(move, this);
|
||||
}
|
||||
|
||||
bool posIsOk() {
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -445,9 +342,9 @@ class Position {
|
|||
int piecesOnBoardCount() {
|
||||
pieceCountOnBoard[Color.black] = pieceCountOnBoard[Color.white] = 0;
|
||||
|
||||
for (int f = 1; f < 3 + 2; f++) {
|
||||
for (int r = 0; r < 8; r++) {
|
||||
int s = f * 8 + r;
|
||||
for (int f = 1; f < fileExNumber; f++) {
|
||||
for (int r = 0; r < rankNumber; r++) {
|
||||
int s = f * rankNumber + r;
|
||||
if (board[s] == Piece.blackStone) {
|
||||
pieceCountOnBoard[Color.black]++;
|
||||
} else if (board[s] == Piece.whiteStone) {
|
||||
|
@ -473,6 +370,16 @@ class Position {
|
|||
return pieceCountOnBoard[Color.black] + pieceCountOnBoard[Color.white];
|
||||
}
|
||||
|
||||
void clearBoard() {
|
||||
for (int i = 0; i < _grid.length; i++) {
|
||||
_grid[i] = Piece.noPiece;
|
||||
}
|
||||
|
||||
for (int i = 0; i < board.length; i++) {
|
||||
board[i] = Piece.noPiece;
|
||||
}
|
||||
}
|
||||
|
||||
int setPosition(Rule newRule) {
|
||||
result = GameResult.pending;
|
||||
|
||||
|
@ -490,13 +397,7 @@ class Position {
|
|||
|
||||
cmdline = "";
|
||||
|
||||
for (int i = 0; i < _grid.length; i++) {
|
||||
_grid[i] = Piece.noPiece;
|
||||
}
|
||||
|
||||
for (int i = 0; i < board.length; i++) {
|
||||
board[i] = Piece.noPiece;
|
||||
}
|
||||
clearBoard();
|
||||
|
||||
if (piecesOnBoardCount() == -1) {
|
||||
return -1;
|
||||
|
@ -509,6 +410,7 @@ class Position {
|
|||
createMoveTable();
|
||||
createMillTable();
|
||||
currentSquare = 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -523,13 +425,7 @@ class Position {
|
|||
winner = Color.nobody;
|
||||
gameOverReason = GameOverReason.noReason;
|
||||
|
||||
for (int i = 0; i < _grid.length; i++) {
|
||||
_grid[i] = Piece.noPiece;
|
||||
}
|
||||
|
||||
for (int i = 0; i < board.length; i++) {
|
||||
board[i] = Piece.noPiece;
|
||||
}
|
||||
clearBoard();
|
||||
|
||||
pieceCountOnBoard[Color.black] = pieceCountOnBoard[Color.white] = 0;
|
||||
pieceCountInHand[Color.black] =
|
||||
|
@ -761,13 +657,13 @@ class Position {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool selectPiece(int s) {
|
||||
bool selectPiece(int sq) {
|
||||
if (phase != Phase.moving) return false;
|
||||
|
||||
if (action != Act.select && action != Act.place) return false;
|
||||
|
||||
if (board[s] == sideToMove()) {
|
||||
currentSquare = s;
|
||||
if (board[sq] == sideToMove()) {
|
||||
currentSquare = sq;
|
||||
action = Act.place;
|
||||
|
||||
return true;
|
||||
|
@ -793,18 +689,19 @@ class Position {
|
|||
bool command(String cmd) {
|
||||
// TODO
|
||||
/*
|
||||
if (sscanf(cmd, "r%1u s%3d t%2u", &ruleIndex, &step, &t) == 3) {
|
||||
if (ruleIndex <= 0 || ruleIndex > N_RULES) {
|
||||
return false;
|
||||
}
|
||||
if (sscanf(cmd, "r%1u s%3d t%2u", &ruleIndex, &step, &t) == 3) {
|
||||
if (ruleIndex <= 0 || ruleIndex > N_RULES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return set_position(&RULES[ruleIndex - 1]) >= 0 ? true : false;
|
||||
}
|
||||
return set_position(&RULES[ruleIndex - 1]) >= 0 ? true : false;
|
||||
}
|
||||
*/
|
||||
print("position: command = $cmd");
|
||||
|
||||
if (cmd.length > 6 && cmd.substring(0, 5) == "Player") {
|
||||
if (cmd[6] == '1') {
|
||||
if (cmd.length > "Player".length &&
|
||||
cmd.substring(0, "Player".length - 1) == "Player") {
|
||||
if (cmd["Player".length] == '1') {
|
||||
return resign(Color.black);
|
||||
} else {
|
||||
return resign(Color.white);
|
||||
|
@ -1449,7 +1346,7 @@ class Position {
|
|||
|
||||
int inHowManyMills(int s, String c, {int squareSelected = 0}) {
|
||||
int n = 0;
|
||||
String locbak = Piece.noPiece;
|
||||
String ptBak = Piece.noPiece;
|
||||
|
||||
assert(0 <= squareSelected && squareSelected < sqNumber);
|
||||
|
||||
|
@ -1458,20 +1355,19 @@ class Position {
|
|||
}
|
||||
|
||||
if (squareSelected != 0) {
|
||||
locbak = board[squareSelected];
|
||||
ptBak = board[squareSelected];
|
||||
board[squareSelected] =
|
||||
_grid[squareToIndex[squareSelected]] = Piece.noPiece;
|
||||
}
|
||||
|
||||
for (int l = 0; l < lineDirectionNumber; l++) {
|
||||
// TODO: right?
|
||||
if (c == board[millTable[s][l][0]] && c == board[millTable[s][l][1]]) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
if (squareSelected != 0) {
|
||||
board[squareSelected] = _grid[squareToIndex[squareSelected]] = locbak;
|
||||
board[squareSelected] = _grid[squareToIndex[squareSelected]] = ptBak;
|
||||
}
|
||||
|
||||
return n;
|
||||
|
@ -1490,7 +1386,6 @@ class Position {
|
|||
idx[2] = millTable[s][i][1];
|
||||
|
||||
// no mill
|
||||
// TODO: right?
|
||||
if (!(m == board[idx[1]] && m == board[idx[2]])) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1571,45 +1466,37 @@ class Position {
|
|||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 验证移动棋子的着法是否合法
|
||||
bool validateMove(int from, int to) {
|
||||
// 移动的棋子的选手,应该是当前方
|
||||
//if (Color.of(_board[from]) != _sideToMove) return false;
|
||||
return true;
|
||||
//(StepValidate.validate(this, Move(from, to)));
|
||||
}
|
||||
|
||||
bool regret() {
|
||||
// TODO
|
||||
final lastMove = _recorder.removeLast();
|
||||
if (lastMove == null) return false;
|
||||
|
||||
_grid[lastMove.from] = _grid[lastMove.to];
|
||||
_grid[lastMove.to] = lastMove.captured;
|
||||
_grid[lastMove.to] = lastMove.removed;
|
||||
board[lastMove.from] = board[lastMove.to];
|
||||
board[lastMove.to] = lastMove.captured;
|
||||
board[lastMove.to] = lastMove.removed;
|
||||
|
||||
changeSideToMove();
|
||||
|
||||
final counterMarks = MillRecorder.fromCounterMarks(lastMove.counterMarks);
|
||||
final counterMarks = GameRecorder.fromCounterMarks(lastMove.counterMarks);
|
||||
_recorder.halfMove = counterMarks.halfMove;
|
||||
_recorder.fullMove = counterMarks.fullMove;
|
||||
|
||||
if (lastMove.captured != Piece.noPiece) {
|
||||
if (lastMove.removed != Piece.noPiece) {
|
||||
//
|
||||
// 查找上一个吃子局面(或开局),NativeEngine 需要
|
||||
final tempPosition = Position.clone(this);
|
||||
|
||||
final moves = _recorder.reverseMovesToPrevCapture();
|
||||
final moves = _recorder.reverseMovesToPrevRemove();
|
||||
moves.forEach((move) {
|
||||
//
|
||||
tempPosition._grid[move.from] = tempPosition._grid[move.to];
|
||||
tempPosition._grid[move.to] = move.captured;
|
||||
tempPosition._grid[move.to] = move.removed;
|
||||
|
||||
tempPosition._sideToMove = Color.opponent(tempPosition._sideToMove);
|
||||
});
|
||||
|
||||
_recorder.lastCapturedPosition = tempPosition.fen();
|
||||
_recorder.lastPositionWithRemove = tempPosition.fen();
|
||||
}
|
||||
|
||||
result = GameResult.pending;
|
||||
|
@ -1617,20 +1504,20 @@ class Position {
|
|||
return true;
|
||||
}
|
||||
|
||||
String movesSinceLastCaptured() {
|
||||
String movesSinceLastRemove() {
|
||||
//
|
||||
var steps = '', posAfterLastCaptured = 0;
|
||||
var moves = '', posAfterLastRemove = 0;
|
||||
|
||||
for (var i = _recorder.stepsCount - 1; i >= 0; i--) {
|
||||
if (_recorder.stepAt(i).captured != Piece.noPiece) break;
|
||||
posAfterLastCaptured = i;
|
||||
for (var i = _recorder.movesCount - 1; i >= 0; i--) {
|
||||
if (_recorder.stepAt(i).removed != Piece.noPiece) break;
|
||||
posAfterLastRemove = i;
|
||||
}
|
||||
|
||||
for (var i = posAfterLastCaptured; i < _recorder.stepsCount; i++) {
|
||||
steps += ' ${_recorder.stepAt(i).move}';
|
||||
for (var i = posAfterLastRemove; i < _recorder.movesCount; i++) {
|
||||
moves += ' ${_recorder.stepAt(i).move}';
|
||||
}
|
||||
|
||||
return steps.length > 0 ? steps.substring(1) : '';
|
||||
return moves.length > 0 ? moves.substring(1) : '';
|
||||
}
|
||||
|
||||
get manualText => _recorder.buildManualText();
|
||||
|
@ -1649,5 +1536,5 @@ class Position {
|
|||
|
||||
get lastMove => _recorder.last;
|
||||
|
||||
get lastCapturedPosition => _recorder.lastCapturedPosition;
|
||||
get lastPositionWithRemove => _recorder.lastPositionWithRemove;
|
||||
}
|
||||
|
|
|
@ -20,16 +20,16 @@
|
|||
import 'mill.dart';
|
||||
import 'position.dart';
|
||||
|
||||
class MillRecorder {
|
||||
class GameRecorder {
|
||||
//
|
||||
// 无吃子步数、总回合数
|
||||
int halfMove, fullMove;
|
||||
String lastCapturedPosition;
|
||||
String lastPositionWithRemove;
|
||||
final _history = <Move>[];
|
||||
|
||||
MillRecorder(
|
||||
{this.halfMove = 0, this.fullMove = 0, this.lastCapturedPosition});
|
||||
MillRecorder.fromCounterMarks(String marks) {
|
||||
GameRecorder(
|
||||
{this.halfMove = 0, this.fullMove = 0, this.lastPositionWithRemove});
|
||||
GameRecorder.fromCounterMarks(String marks) {
|
||||
//
|
||||
var segments = marks.split(' ');
|
||||
if (segments.length != 2) {
|
||||
|
@ -43,9 +43,9 @@ class MillRecorder {
|
|||
throw 'Error: Invalid Counter Marks: $marks';
|
||||
}
|
||||
}
|
||||
void stepIn(Move move, Position position) {
|
||||
void moveIn(Move move, Position position) {
|
||||
//
|
||||
if (move.captured != Piece.noPiece) {
|
||||
if (move.removed != Piece.noPiece) {
|
||||
halfMove = 0;
|
||||
} else {
|
||||
halfMove++;
|
||||
|
@ -59,8 +59,8 @@ class MillRecorder {
|
|||
|
||||
_history.add(move);
|
||||
|
||||
if (move.captured != Piece.noPiece) {
|
||||
lastCapturedPosition = position.fen();
|
||||
if (move.removed != Piece.noPiece) {
|
||||
lastPositionWithRemove = position.fen();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,12 +71,12 @@ class MillRecorder {
|
|||
|
||||
get last => _history.isEmpty ? null : _history.last;
|
||||
|
||||
List<Move> reverseMovesToPrevCapture() {
|
||||
List<Move> reverseMovesToPrevRemove() {
|
||||
//
|
||||
List<Move> moves = [];
|
||||
|
||||
for (var i = _history.length - 1; i >= 0; i--) {
|
||||
if (_history[i].captured != Piece.noPiece) break;
|
||||
if (_history[i].removed != Piece.noPiece) break;
|
||||
moves.add(_history[i]);
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ class MillRecorder {
|
|||
|
||||
Move stepAt(int index) => _history[index];
|
||||
|
||||
get stepsCount => _history.length;
|
||||
get movesCount => _history.length;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:sanmill/common/misc.dart';
|
||||
import 'package:sanmill/common/algorithm.dart';
|
||||
|
||||
enum MoveType { place, move, remove }
|
||||
|
||||
|
@ -91,13 +91,20 @@ const lineDirectionNumber = 3;
|
|||
enum File { A, B, C }
|
||||
|
||||
const fileNumber = 3;
|
||||
const fileExNumber = fileNumber + 2;
|
||||
|
||||
enum Rank { rank_1, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7, rank_8 }
|
||||
|
||||
const rankNumber = 8;
|
||||
|
||||
bool isOk(int sq) {
|
||||
return sq == 0 || (sq >= 8 && sq <= 31); // TODO: SQ_NONE?
|
||||
bool ret = (sq == 0 || (sq >= 8 && sq <= 31));
|
||||
|
||||
if (ret == false) {
|
||||
print("$sq is not OK");
|
||||
}
|
||||
|
||||
return ret; // TODO: SQ_NONE?
|
||||
}
|
||||
|
||||
int fileOf(int sq) {
|
||||
|
@ -118,20 +125,6 @@ int toSq(int move) {
|
|||
return (move & 0x00FF);
|
||||
}
|
||||
|
||||
MoveType typeOf(int move) {
|
||||
if (move < 0) {
|
||||
return MoveType.remove;
|
||||
} else if (move & 0x1f00 > 0) {
|
||||
return MoveType.move;
|
||||
}
|
||||
|
||||
return MoveType.place; // m & 0x00ff
|
||||
}
|
||||
|
||||
int makeMove(int fromSq, int toSq) {
|
||||
return (fromSq << 8) + toSq;
|
||||
}
|
||||
|
||||
int reverseMove(int move) {
|
||||
return makeMove(toSq(move), fromSq(move));
|
||||
int makeMove(int from, int to) {
|
||||
return (from << 8) + to;
|
||||
}
|
|
@ -18,25 +18,23 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sanmill/style/colors.dart';
|
||||
import 'package:sanmill/widgets/board.dart';
|
||||
|
||||
import '../board/painter_base.dart';
|
||||
import '../common/properties.dart';
|
||||
import 'board_widget.dart';
|
||||
import 'painter_base.dart';
|
||||
|
||||
class BoardPainter extends PainterBase {
|
||||
//
|
||||
class BoardPainter extends PiecesBasePainter {
|
||||
BoardPainter({@required double width}) : super(width: width);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
//
|
||||
doPaint(
|
||||
canvas,
|
||||
thePaint,
|
||||
gridWidth,
|
||||
squareWidth,
|
||||
offsetX: BoardWidget.padding + squareWidth / 2,
|
||||
offsetY: BoardWidget.padding + BoardWidget.digitsHeight + squareWidth / 2,
|
||||
offsetX: Board.padding + squareWidth / 2,
|
||||
offsetY: Board.padding + Board.digitsHeight + squareWidth / 2,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -53,8 +51,7 @@ class BoardPainter extends PainterBase {
|
|||
double offsetX,
|
||||
double offsetY,
|
||||
}) {
|
||||
//
|
||||
paint.color = Properties.boardLineColor;
|
||||
paint.color = UIColors.boardLineColor;
|
||||
paint.style = PaintingStyle.stroke;
|
||||
|
||||
const double borderLineWidth = 2.0;
|
|
@ -18,18 +18,16 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sanmill/widgets/board.dart';
|
||||
|
||||
import 'board_widget.dart';
|
||||
|
||||
abstract class PainterBase extends CustomPainter {
|
||||
//
|
||||
abstract class PiecesBasePainter extends CustomPainter {
|
||||
final double width;
|
||||
|
||||
final thePaint = Paint();
|
||||
final gridWidth;
|
||||
final squareWidth;
|
||||
|
||||
PainterBase({@required this.width})
|
||||
: gridWidth = (width - BoardWidget.padding * 2),
|
||||
squareWidth = (width - BoardWidget.padding * 2) / 7;
|
||||
PiecesBasePainter({@required this.width})
|
||||
: gridWidth = (width - Board.padding * 2),
|
||||
squareWidth = (width - Board.padding * 2) / 7;
|
||||
}
|
|
@ -18,21 +18,20 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sanmill/mill/mill.dart';
|
||||
import 'package:sanmill/mill/position.dart';
|
||||
import 'package:sanmill/style/colors.dart';
|
||||
import 'package:sanmill/widgets/board.dart';
|
||||
|
||||
import '../board/painter_base.dart';
|
||||
import '../common/properties.dart';
|
||||
import '../mill/mill.dart';
|
||||
import '../mill/position.dart';
|
||||
import 'board_widget.dart';
|
||||
import 'painter_base.dart';
|
||||
|
||||
class PiecePaintStub {
|
||||
class PiecePaintPair {
|
||||
final String piece;
|
||||
final Offset pos;
|
||||
PiecePaintStub({this.piece, this.pos});
|
||||
PiecePaintPair({this.piece, this.pos});
|
||||
}
|
||||
|
||||
class PiecesPainter extends PainterBase {
|
||||
//
|
||||
class PiecesPainter extends PiecesBasePainter {
|
||||
final Position position;
|
||||
final int focusIndex, blurIndex;
|
||||
|
||||
|
@ -41,8 +40,8 @@ class PiecesPainter extends PainterBase {
|
|||
PiecesPainter({
|
||||
@required double width,
|
||||
@required this.position,
|
||||
this.focusIndex = Move.invalidValue,
|
||||
this.blurIndex = Move.invalidValue,
|
||||
this.focusIndex = Move.invalidMove,
|
||||
this.blurIndex = Move.invalidMove,
|
||||
}) : super(width: width) {
|
||||
//
|
||||
pieceWidth = squareWidth * 0.9; // 棋子大小
|
||||
|
@ -50,7 +49,6 @@ class PiecesPainter extends PainterBase {
|
|||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
//
|
||||
doPaint(
|
||||
canvas,
|
||||
thePaint,
|
||||
|
@ -59,8 +57,8 @@ class PiecesPainter extends PainterBase {
|
|||
squareWidth: squareWidth,
|
||||
pieceWidth: pieceWidth,
|
||||
// 棋子放在线上中央
|
||||
offsetX: BoardWidget.padding + squareWidth / 2,
|
||||
offsetY: BoardWidget.padding + BoardWidget.digitsHeight + squareWidth / 2,
|
||||
offsetX: Board.padding + squareWidth / 2,
|
||||
offsetY: Board.padding + Board.digitsHeight + squareWidth / 2,
|
||||
focusIndex: focusIndex,
|
||||
blurIndex: blurIndex,
|
||||
);
|
||||
|
@ -81,19 +79,18 @@ class PiecesPainter extends PainterBase {
|
|||
double pieceWidth,
|
||||
double offsetX,
|
||||
double offsetY,
|
||||
int focusIndex = Move.invalidValue,
|
||||
int blurIndex = Move.invalidValue,
|
||||
int focusIndex = Move.invalidMove,
|
||||
int blurIndex = Move.invalidMove,
|
||||
}) {
|
||||
//
|
||||
final left = offsetX;
|
||||
final top = offsetY;
|
||||
|
||||
final shadowPath = Path();
|
||||
final piecesToDraw = <PiecePaintStub>[];
|
||||
final piecesToDraw = <PiecePaintPair>[];
|
||||
|
||||
// 在棋盘上画棋子
|
||||
for (var row = 0; row < 7; row++) {
|
||||
//
|
||||
for (var col = 0; col < 7; col++) {
|
||||
//
|
||||
final piece = position.pieceOnGrid(row * 7 + col); // 初始状态无棋子
|
||||
|
@ -102,7 +99,7 @@ class PiecesPainter extends PainterBase {
|
|||
|
||||
var pos = Offset(left + squareWidth * col, top + squareWidth * row);
|
||||
|
||||
piecesToDraw.add(PiecePaintStub(piece: piece, pos: pos));
|
||||
piecesToDraw.add(PiecePaintPair(piece: piece, pos: pos));
|
||||
|
||||
shadowPath.addOval(
|
||||
Rect.fromCenter(center: pos, width: pieceWidth, height: pieceWidth),
|
||||
|
@ -130,22 +127,22 @@ class PiecesPainter extends PainterBase {
|
|||
// 绘制棋子边界
|
||||
switch (pps.piece) {
|
||||
case Piece.blackStone:
|
||||
paint.color = Properties.blackPieceBorderColor;
|
||||
paint.color = UIColors.blackPieceBorderColor;
|
||||
canvas.drawCircle(pps.pos, pieceRadius, paint); // 临时调试用
|
||||
paint.color = Properties.blackPieceColor;
|
||||
paint.color = UIColors.blackPieceColor;
|
||||
canvas.drawCircle(pps.pos, pieceInnerRadius, paint);
|
||||
break;
|
||||
case Piece.whiteStone:
|
||||
paint.color = Properties.whitePieceBorderColor;
|
||||
paint.color = UIColors.whitePieceBorderColor;
|
||||
canvas.drawCircle(pps.pos, pieceRadius, paint); // 临时调试用
|
||||
paint.color = Properties.whitePieceColor;
|
||||
paint.color = UIColors.whitePieceColor;
|
||||
canvas.drawCircle(pps.pos, pieceInnerRadius, paint);
|
||||
break;
|
||||
case Piece.ban:
|
||||
print("pps.piece is Ban");
|
||||
paint.color = Properties.banBorderColor;
|
||||
paint.color = UIColors.banBorderColor;
|
||||
// TODO
|
||||
paint.color = Properties.banColor;
|
||||
paint.color = UIColors.banColor;
|
||||
canvas.drawLine(new Offset(0, 0),
|
||||
new Offset(pieceInnerRadius, pieceInnerRadius), paint);
|
||||
canvas.drawLine(new Offset(pieceInnerRadius, 0),
|
||||
|
@ -155,33 +152,15 @@ class PiecesPainter extends PainterBase {
|
|||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
final textSpan = TextSpan(text: Piece.Names[pps.piece], style: textStyle);
|
||||
|
||||
final textPainter = TextPainter(
|
||||
text: textSpan,
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
|
||||
|
||||
final metric = textPainter.computeLineMetrics()[0];
|
||||
final textSize = textPainter.size;
|
||||
|
||||
// 从顶上算,文字的 Baseline 在 2/3 高度线上
|
||||
final textOffset = pps.pos - Offset(textSize.width / 2, metric.baseline - textSize.height / 3);
|
||||
|
||||
textPainter.paint(canvas, textOffset);
|
||||
*/
|
||||
});
|
||||
|
||||
// draw focus and blur position
|
||||
|
||||
if (focusIndex != Move.invalidValue) {
|
||||
if (focusIndex != Move.invalidMove) {
|
||||
//
|
||||
final int row = focusIndex ~/ 7, column = focusIndex % 7;
|
||||
|
||||
paint.color = Properties.focusPositionColor;
|
||||
paint.color = UIColors.focusPositionColor;
|
||||
paint.style = PaintingStyle.stroke;
|
||||
paint.strokeWidth = 2;
|
||||
|
||||
|
@ -192,11 +171,10 @@ class PiecesPainter extends PainterBase {
|
|||
);
|
||||
}
|
||||
|
||||
if (blurIndex != Move.invalidValue) {
|
||||
//
|
||||
if (blurIndex != Move.invalidMove) {
|
||||
final row = blurIndex ~/ 7, column = blurIndex % 7;
|
||||
|
||||
paint.color = Properties.blurPositionColor;
|
||||
paint.color = UIColors.blurPositionColor;
|
||||
paint.style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawCircle(
|
|
@ -29,7 +29,6 @@ class Audios {
|
|||
//
|
||||
try {
|
||||
if (_bgmPlayer == null) {
|
||||
//
|
||||
_fixedBgmPlayer = AudioPlayer();
|
||||
_bgmPlayer =
|
||||
AudioCache(prefix: 'audios/', fixedPlayer: _fixedBgmPlayer);
|
||||
|
@ -43,7 +42,6 @@ class Audios {
|
|||
}
|
||||
|
||||
static playTone(String fileName) async {
|
||||
//
|
||||
try {
|
||||
if (_tonePlayer == null) {
|
||||
//
|
||||
|
|
|
@ -32,7 +32,6 @@ class Player extends RankItem {
|
|||
static get shared => _instance;
|
||||
|
||||
static Future<Player> loadProfile() async {
|
||||
//
|
||||
if (_instance == null) {
|
||||
_instance = Player();
|
||||
await _instance._load();
|
||||
|
@ -42,7 +41,6 @@ class Player extends RankItem {
|
|||
}
|
||||
|
||||
_load() async {
|
||||
//
|
||||
final profile = await Profile.shared();
|
||||
|
||||
_uuid = profile['player-uuid'];
|
||||
|
@ -56,14 +54,14 @@ class Player extends RankItem {
|
|||
|
||||
name = values['name'] ?? '无名英雄';
|
||||
winCloudEngine = values['win_cloud_engine'] ?? 0;
|
||||
winPhoneAi = values['win_phone_ai'] ?? 0;
|
||||
winAi = values['win_ai'] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
Player() : super.empty();
|
||||
|
||||
Future<void> increaseWinPhoneAi() async {
|
||||
winPhoneAi++;
|
||||
Future<void> increaseWinAi() async {
|
||||
winAi++;
|
||||
await saveAndUpload();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,33 +23,33 @@ import 'dart:io';
|
|||
class RankItem {
|
||||
//
|
||||
String name;
|
||||
int winCloudEngine, winPhoneAi;
|
||||
int winCloudEngine, winAi;
|
||||
|
||||
RankItem(Map<String, dynamic> values) {
|
||||
name = values['name'] ?? '无名英雄';
|
||||
winCloudEngine = values['win_cloud_engine'] ?? 0;
|
||||
winPhoneAi = values['win_phone_ai'] ?? 0;
|
||||
winAi = values['win_ai'] ?? 0;
|
||||
}
|
||||
|
||||
RankItem.empty() {
|
||||
name = '无名英雄';
|
||||
winCloudEngine = 0;
|
||||
winPhoneAi = 0;
|
||||
winAi = 0;
|
||||
}
|
||||
|
||||
RankItem.mock() {
|
||||
name = '我是英雄';
|
||||
winCloudEngine = 3;
|
||||
winPhoneAi = 12;
|
||||
winAi = 12;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
'name': name,
|
||||
'win_cloud_engine': winCloudEngine,
|
||||
'win_phone_ai': winPhoneAi,
|
||||
'win_ai': winAi,
|
||||
};
|
||||
|
||||
get score => winCloudEngine * 30 + winPhoneAi * 5;
|
||||
get score => winCloudEngine * 30 + winAi * 5;
|
||||
}
|
||||
|
||||
class Ranks {
|
||||
|
@ -113,7 +113,7 @@ class Ranks {
|
|||
'uuid': uuid,
|
||||
'name': rank.name,
|
||||
'win_cloud_engine': '${rank.winCloudEngine}',
|
||||
'win_phone_ai': '${rank.winPhoneAi}',
|
||||
'win_ai': '${rank.winAi}',
|
||||
});
|
||||
|
||||
final httpClient = HttpClient();
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Properties {
|
||||
class UIColors {
|
||||
//
|
||||
static const logoColor = Color(0xFF6D000D);
|
||||
|
|
@ -18,14 +18,14 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sanmill/mill/game.dart';
|
||||
import 'package:sanmill/painting/board_painter.dart';
|
||||
import 'package:sanmill/painting/pieces_painter.dart';
|
||||
import 'package:sanmill/style/colors.dart';
|
||||
|
||||
import '../common/properties.dart';
|
||||
import '../game/battle.dart';
|
||||
import 'board_painter.dart';
|
||||
import 'pieces_painter.dart';
|
||||
import 'words_on_board.dart';
|
||||
|
||||
class BoardWidget extends StatelessWidget {
|
||||
class Board extends StatelessWidget {
|
||||
//
|
||||
static const padding = 5.0;
|
||||
static const digitsHeight = 0.0;
|
||||
|
@ -36,8 +36,7 @@ class BoardWidget extends StatelessWidget {
|
|||
final double height;
|
||||
final Function(BuildContext, int) onBoardTap;
|
||||
|
||||
BoardWidget({@required this.width, @required this.onBoardTap})
|
||||
: height = width;
|
||||
Board({@required this.width, @required this.onBoardTap}) : height = width;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -47,15 +46,15 @@ class BoardWidget extends StatelessWidget {
|
|||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(boardBorderRadius),
|
||||
color: Properties.boardBackgroundColor,
|
||||
color: UIColors.boardBackgroundColor,
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: BoardPainter(width: width),
|
||||
foregroundPainter: PiecesPainter(
|
||||
width: width,
|
||||
position: Battle.shared.position,
|
||||
focusIndex: Battle.shared.focusIndex,
|
||||
blurIndex: Battle.shared.blurIndex,
|
||||
position: Game.shared.position,
|
||||
focusIndex: Game.shared.focusIndex,
|
||||
blurIndex: Game.shared.blurIndex,
|
||||
),
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(
|
|
@ -18,8 +18,7 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../common/properties.dart';
|
||||
import 'package:sanmill/style/colors.dart';
|
||||
|
||||
class EditPage extends StatefulWidget {
|
||||
//
|
||||
|
@ -58,7 +57,7 @@ class _EditPageState extends State<EditPage> {
|
|||
//
|
||||
final inputBorder = OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
borderSide: BorderSide(color: Properties.secondaryColor),
|
||||
borderSide: BorderSide(color: UIColors.secondaryColor),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
|
@ -72,7 +71,7 @@ class _EditPageState extends State<EditPage> {
|
|||
)
|
||||
],
|
||||
),
|
||||
backgroundColor: Properties.lightBackgroundColor,
|
||||
backgroundColor: UIColors.lightBackgroundColor,
|
||||
body: Container(
|
||||
margin: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
|
@ -86,7 +85,7 @@ class _EditPageState extends State<EditPage> {
|
|||
focusedBorder: inputBorder,
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Properties.primaryColor, fontSize: 16, fontFamily: ''),
|
||||
color: UIColors.primaryColor, fontSize: 16, fontFamily: ''),
|
||||
onSubmitted: (input) => onSubmit(input),
|
||||
focusNode: _commentFocus,
|
||||
),
|
|
@ -18,53 +18,49 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sanmill/engine/analyze.dart';
|
||||
import 'package:sanmill/engine/engine.dart';
|
||||
import 'package:sanmill/engine/native_engine.dart';
|
||||
import 'package:sanmill/main.dart';
|
||||
import 'package:sanmill/mill/game.dart';
|
||||
import 'package:sanmill/mill/mill.dart';
|
||||
import 'package:sanmill/mill/types.dart';
|
||||
import 'package:sanmill/services/player.dart';
|
||||
import 'package:sanmill/style/colors.dart';
|
||||
import 'package:sanmill/style/toast.dart';
|
||||
|
||||
import '../board/board_widget.dart';
|
||||
import '../common/properties.dart';
|
||||
import '../common/toast.dart';
|
||||
import '../common/types.dart';
|
||||
import '../engine/analysis.dart';
|
||||
import '../engine/engine.dart';
|
||||
import '../engine/native_engine.dart';
|
||||
import '../game/battle.dart';
|
||||
import '../main.dart';
|
||||
import '../mill/mill.dart';
|
||||
import '../services/player.dart';
|
||||
import 'board.dart';
|
||||
import 'settings_page.dart';
|
||||
|
||||
class BattlePage extends StatefulWidget {
|
||||
class GamePage extends StatefulWidget {
|
||||
//
|
||||
static double boardMargin = 10.0, screenPaddingH = 10.0;
|
||||
|
||||
final EngineType engineType;
|
||||
final AiEngine engine;
|
||||
|
||||
BattlePage(this.engineType) : engine = NativeEngine();
|
||||
GamePage(this.engineType) : engine = NativeEngine();
|
||||
|
||||
@override
|
||||
_BattlePageState createState() => _BattlePageState();
|
||||
_GamePageState createState() => _GamePageState();
|
||||
}
|
||||
|
||||
class _BattlePageState extends State<BattlePage> {
|
||||
class _GamePageState extends State<GamePage> {
|
||||
//
|
||||
String _status = '';
|
||||
bool _analysising = false;
|
||||
|
||||
//static int flag = 0;
|
||||
bool _searching = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
//
|
||||
super.initState();
|
||||
Battle.shared.init();
|
||||
|
||||
Game.shared.init();
|
||||
widget.engine.startup();
|
||||
}
|
||||
|
||||
changeStatus(String status) => setState(() => _status = status);
|
||||
|
||||
onBoardTap(BuildContext context, int index) {
|
||||
final position = Battle.shared.position;
|
||||
final position = Game.shared.position;
|
||||
|
||||
int sq = indexToSquare[index];
|
||||
|
||||
|
@ -75,13 +71,13 @@ class _BattlePageState extends State<BattlePage> {
|
|||
}
|
||||
|
||||
// AI 走棋或正在搜索时,点击无效
|
||||
if (Battle.shared.isAIsTurn() || Battle.shared.aiIsSearching()) {
|
||||
if (Game.shared.isAiToMove() || Game.shared.aiIsSearching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果未开局则开局
|
||||
if (position.phase == Phase.ready) {
|
||||
Battle.shared.gameStart();
|
||||
Game.shared.start();
|
||||
}
|
||||
|
||||
// 判断执行选子、落子或去子
|
||||
|
@ -92,11 +88,11 @@ class _BattlePageState extends State<BattlePage> {
|
|||
if (position.putPiece(sq)) {
|
||||
if (position.action == Act.remove) {
|
||||
// 播放成三音效
|
||||
//playSound(GAME_SOUND_MILL, position.side_to_move());
|
||||
//Audios.playTone('mill.mp3');
|
||||
changeStatus('请吃子');
|
||||
} else {
|
||||
// 播放移动棋子音效
|
||||
//playSound(GAME_SOUND_DROG, position.side_to_move());
|
||||
//Audios.playTone('put.mp3');
|
||||
changeStatus('已落子');
|
||||
}
|
||||
result = true;
|
||||
|
@ -112,19 +108,16 @@ class _BattlePageState extends State<BattlePage> {
|
|||
continue select;
|
||||
select:
|
||||
case Act.select:
|
||||
//piece = qgraphicsitem_cast<PieceItem *>(item);
|
||||
//if (!piece)
|
||||
//break;
|
||||
if (position.selectPiece(sq)) {
|
||||
// 播放选子音效
|
||||
//playSound(GAME_SOUND_SELECT, position.side_to_move());
|
||||
Battle.shared.select(index);
|
||||
//Audios.playTone('select.mp3');
|
||||
Game.shared.select(index);
|
||||
result = true;
|
||||
print("selectPiece: [$sq]");
|
||||
changeStatus('请落子');
|
||||
} else {
|
||||
// 播放禁止音效
|
||||
//playSound(GAME_SOUND_BANNED, position.side_to_move());
|
||||
//Audios.playTone('banned.mp3');
|
||||
print("selectPiece: skip [$sq]");
|
||||
changeStatus('选择的子不对');
|
||||
}
|
||||
|
@ -133,13 +126,13 @@ class _BattlePageState extends State<BattlePage> {
|
|||
case Act.remove:
|
||||
if (position.removePiece(sq)) {
|
||||
// 播放音效
|
||||
//playSound(GAME_SOUND_REMOVE, position.side_to_move());
|
||||
//Audios.playTone('remove.mp3');
|
||||
result = true;
|
||||
print("removePiece: [$sq]");
|
||||
changeStatus('已吃子');
|
||||
} else {
|
||||
// 播放禁止音效
|
||||
//playSound(GAME_SOUND_BANNED, position.side_to_move());
|
||||
//Audios.playTone('banned.mp3');
|
||||
print("removePiece: skip [$sq]");
|
||||
changeStatus('不能吃这个子');
|
||||
}
|
||||
|
@ -151,135 +144,46 @@ class _BattlePageState extends State<BattlePage> {
|
|||
}
|
||||
|
||||
if (result) {
|
||||
Battle.shared.cmdlist.add(position.cmdline);
|
||||
Game.shared.moveHistory.add(position.cmdline);
|
||||
|
||||
// 发信号更新状态栏
|
||||
setState(() {});
|
||||
//message = QString::fromStdString(getTips());
|
||||
//emit statusBarChanged(message);
|
||||
|
||||
// 将新增的棋谱行插入到ListModel
|
||||
/*
|
||||
currentRow = manualListModel.rowCount() - 1;
|
||||
int k = 0;
|
||||
|
||||
// 输出命令行
|
||||
for (const auto & i : *(cmd_list())) {
|
||||
// 跳过已添加的,因标准list容器没有下标
|
||||
if (k++ <= currentRow)
|
||||
continue;
|
||||
manualListModel.insertRow(++currentRow);
|
||||
manualListModel.setData(manualListModel.index(currentRow), i.c_str());
|
||||
}
|
||||
*/
|
||||
|
||||
// 播放胜利或失败音效
|
||||
/*
|
||||
String winner = position.winner;
|
||||
if (winner != Color.nobody &&
|
||||
(manualListModel.data(manualListModel.index(currentRow - 1)))
|
||||
.toString()
|
||||
.contains("Time over.")) playSound(GAME_SOUND_WIN, winner);
|
||||
*/
|
||||
|
||||
// AI设置
|
||||
// 如果还未决出胜负
|
||||
if (position.winner == Color.nobody) {
|
||||
// Color.black is TODO
|
||||
//resumeAiThreads(position.sideToMove());
|
||||
engineToGo();
|
||||
}
|
||||
}
|
||||
|
||||
Battle.shared.sideToMove = position.sideToMove();
|
||||
Game.shared.sideToMove = position.sideToMove();
|
||||
|
||||
setState(() {});
|
||||
|
||||
return result;
|
||||
|
||||
// TODO:
|
||||
|
||||
// 仅 Position 中的 side 指示一方能动棋
|
||||
//if (position.side != Color.black) return;
|
||||
|
||||
final tapedPiece = position.pieceOnGrid(index);
|
||||
print("Tap piece $tapedPiece at <$index>");
|
||||
|
||||
switch (position.phase) {
|
||||
case Phase.placing:
|
||||
engineToGo();
|
||||
break;
|
||||
case Phase.moving:
|
||||
// 之前已经有棋子被选中了
|
||||
if (Battle.shared.focusIndex != Move.invalidValue &&
|
||||
Color.of(position.pieceOnGrid(Battle.shared.focusIndex)) ==
|
||||
Color.black) {
|
||||
//
|
||||
// 当前点击的棋子和之前已经选择的是同一个位置
|
||||
if (Battle.shared.focusIndex == index) return;
|
||||
|
||||
// 之前已经选择的棋子和现在点击的棋子是同一边的,说明是选择另外一个棋子
|
||||
final focusPiece = position.pieceOnGrid(Battle.shared.focusIndex);
|
||||
|
||||
if (Color.isSameColor(focusPiece, tapedPiece)) {
|
||||
//
|
||||
Battle.shared.select(index);
|
||||
//
|
||||
} else if (Battle.shared.move(Battle.shared.focusIndex, index)) {
|
||||
// 现在点击的棋子和上一次选择棋子不同边,要么是吃子,要么是移动棋子到空白处
|
||||
final result = Battle.shared.scanBattleResult();
|
||||
|
||||
switch (result) {
|
||||
case GameResult.pending:
|
||||
engineToGo();
|
||||
break;
|
||||
case GameResult.win:
|
||||
gotWin();
|
||||
break;
|
||||
case GameResult.lose:
|
||||
gotLose();
|
||||
break;
|
||||
case GameResult.draw:
|
||||
gotDraw();
|
||||
break;
|
||||
}
|
||||
}
|
||||
//
|
||||
} else {
|
||||
// 之前未选择棋子,现在点击就是选择棋子
|
||||
if (tapedPiece != Piece.noPiece) Battle.shared.select(index);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
engineToGo() async {
|
||||
// TODO
|
||||
while (Battle.shared.position.sideToMove() == Color.white) {
|
||||
changeStatus('电脑思考中...');
|
||||
while (Game.shared.position.sideToMove() == Color.white) {
|
||||
changeStatus('对方思考中...');
|
||||
|
||||
final response = await widget.engine.search(Battle.shared.position);
|
||||
final response = await widget.engine.search(Game.shared.position);
|
||||
|
||||
if (response.type == 'move') {
|
||||
//
|
||||
Move mv = response.value;
|
||||
final Move move = new Move(mv.move);
|
||||
|
||||
//Battle.shared.move = move;
|
||||
Battle.shared.command(move.move);
|
||||
Game.shared.command(move.move);
|
||||
|
||||
final winner = Battle.shared.position.winner;
|
||||
final winner = Game.shared.position.winner;
|
||||
|
||||
switch (winner) {
|
||||
case Color.nobody:
|
||||
if (Battle.shared.position.phase == Phase.placing) {
|
||||
if (Game.shared.position.phase == Phase.placing) {
|
||||
changeStatus('请摆子');
|
||||
} else if (Battle.shared.position.phase == Phase.moving) {
|
||||
} else if (Game.shared.position.phase == Phase.moving) {
|
||||
changeStatus('请走子');
|
||||
}
|
||||
break;
|
||||
|
@ -296,28 +200,22 @@ class _BattlePageState extends State<BattlePage> {
|
|||
gotDraw();
|
||||
break;
|
||||
}
|
||||
//
|
||||
} else {
|
||||
//
|
||||
changeStatus('Error: ${response.type}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newGame() {
|
||||
//
|
||||
confirm() {
|
||||
Navigator.of(context).pop();
|
||||
Battle.shared.newGame();
|
||||
//setState(() {});
|
||||
Game.shared.newGame();
|
||||
changeStatus('新游戏');
|
||||
|
||||
if (Battle.shared.isAIsTurn()) {
|
||||
if (Game.shared.isAiToMove()) {
|
||||
print("New Game: AI's turn.");
|
||||
engineToGo();
|
||||
}
|
||||
|
||||
//setState(() {});
|
||||
}
|
||||
|
||||
cancel() => Navigator.of(context).pop();
|
||||
|
@ -326,7 +224,7 @@ class _BattlePageState extends State<BattlePage> {
|
|||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('新局?', style: TextStyle(color: Properties.primaryColor)),
|
||||
title: Text('新局?', style: TextStyle(color: UIColors.primaryColor)),
|
||||
content: SingleChildScrollView(child: Text('开始新局?')),
|
||||
actions: <Widget>[
|
||||
FlatButton(child: Text('确定'), onPressed: confirm),
|
||||
|
@ -337,33 +235,32 @@ class _BattlePageState extends State<BattlePage> {
|
|||
);
|
||||
}
|
||||
|
||||
analysisPosition() async {
|
||||
analyzePosition() async {
|
||||
//
|
||||
Toast.toast(context, msg: '正在分析局面...', position: ToastPostion.bottom);
|
||||
|
||||
setState(() => _analysising = true);
|
||||
setState(() => _searching = true);
|
||||
|
||||
try {} catch (e) {
|
||||
Toast.toast(context, msg: '错误: $e', position: ToastPostion.bottom);
|
||||
} finally {
|
||||
setState(() => _analysising = false);
|
||||
setState(() => _searching = false);
|
||||
}
|
||||
}
|
||||
|
||||
showAnalysisItems(
|
||||
showAnalyzeItems(
|
||||
BuildContext context, {
|
||||
String title,
|
||||
List<AnalysisItem> items,
|
||||
Function(AnalysisItem item) callback,
|
||||
List<AnalyzeItem> items,
|
||||
Function(AnalyzeItem item) callback,
|
||||
}) {
|
||||
//
|
||||
final List<Widget> children = [];
|
||||
|
||||
for (var item in items) {
|
||||
children.add(
|
||||
ListTile(
|
||||
title: Text(item.stepName, style: TextStyle(fontSize: 18)),
|
||||
subtitle: Text('胜率:${item.winrate}%'),
|
||||
title: Text(item.moveName, style: TextStyle(fontSize: 18)),
|
||||
subtitle: Text('胜率:${item.winRate}%'),
|
||||
trailing: Text('分数:${item.score}'),
|
||||
onTap: () => callback(item),
|
||||
),
|
||||
|
@ -384,7 +281,7 @@ class _BattlePageState extends State<BattlePage> {
|
|||
|
||||
void gotWin() {
|
||||
//
|
||||
Battle.shared.position.result = GameResult.win;
|
||||
Game.shared.position.result = GameResult.win;
|
||||
//Audios.playTone('win.mp3');
|
||||
|
||||
showDialog(
|
||||
|
@ -392,7 +289,7 @@ class _BattlePageState extends State<BattlePage> {
|
|||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('赢了', style: TextStyle(color: Properties.primaryColor)),
|
||||
title: Text('赢了', style: TextStyle(color: UIColors.primaryColor)),
|
||||
content: Text('恭喜您取得了伟大的胜利!'),
|
||||
actions: <Widget>[
|
||||
FlatButton(child: Text('再来一盘'), onPressed: newGame),
|
||||
|
@ -407,12 +304,12 @@ class _BattlePageState extends State<BattlePage> {
|
|||
if (widget.engineType == EngineType.Cloud)
|
||||
Player.shared.increaseWinCloudEngine();
|
||||
else
|
||||
Player.shared.increaseWinPhoneAi();
|
||||
Player.shared.increaseWinAi();
|
||||
}
|
||||
|
||||
void gotLose() {
|
||||
//
|
||||
Battle.shared.position.result = GameResult.lose;
|
||||
Game.shared.position.result = GameResult.lose;
|
||||
//Audios.playTone('lose.mp3');
|
||||
|
||||
showDialog(
|
||||
|
@ -420,7 +317,7 @@ class _BattlePageState extends State<BattlePage> {
|
|||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('输了', style: TextStyle(color: Properties.primaryColor)),
|
||||
title: Text('输了', style: TextStyle(color: UIColors.primaryColor)),
|
||||
content: Text('勇士!坚定战斗,虽败犹荣!'),
|
||||
actions: <Widget>[
|
||||
FlatButton(child: Text('再来一盘'), onPressed: newGame),
|
||||
|
@ -435,14 +332,14 @@ class _BattlePageState extends State<BattlePage> {
|
|||
|
||||
void gotDraw() {
|
||||
//
|
||||
Battle.shared.position.result = GameResult.draw;
|
||||
Game.shared.position.result = GameResult.draw;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('和<EFBFBD><EFBFBD><EFBFBD>', style: TextStyle(color: Properties.primaryColor)),
|
||||
title: Text('和棋', style: TextStyle(color: UIColors.primaryColor)),
|
||||
content: Text('您用自己的力量捍卫了和平!'),
|
||||
actions: <Widget>[
|
||||
FlatButton(child: Text('再来一盘'), onPressed: newGame),
|
||||
|
@ -463,17 +360,17 @@ class _BattlePageState extends State<BattlePage> {
|
|||
|
||||
if (height / width < 16.0 / 9.0) {
|
||||
width = height * 9 / 16;
|
||||
BattlePage.screenPaddingH =
|
||||
(windowSize.width - width) / 2 - BattlePage.boardMargin;
|
||||
GamePage.screenPaddingH =
|
||||
(windowSize.width - width) / 2 - GamePage.boardMargin;
|
||||
}
|
||||
}
|
||||
|
||||
Widget createPageHeader() {
|
||||
//
|
||||
final titleStyle =
|
||||
TextStyle(fontSize: 28, color: Properties.darkTextPrimaryColor);
|
||||
TextStyle(fontSize: 28, color: UIColors.darkTextPrimaryColor);
|
||||
final subTitleStyle =
|
||||
TextStyle(fontSize: 16, color: Properties.darkTextSecondaryColor);
|
||||
TextStyle(fontSize: 16, color: UIColors.darkTextSecondaryColor);
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: SanmillApp.StatusBarHeight),
|
||||
|
@ -483,7 +380,7 @@ class _BattlePageState extends State<BattlePage> {
|
|||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.arrow_back,
|
||||
color: Properties.darkTextPrimaryColor),
|
||||
color: UIColors.darkTextPrimaryColor),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
Expanded(child: SizedBox()),
|
||||
|
@ -493,8 +390,8 @@ class _BattlePageState extends State<BattlePage> {
|
|||
style: titleStyle),
|
||||
Expanded(child: SizedBox()),
|
||||
IconButton(
|
||||
icon: Icon(Icons.settings,
|
||||
color: Properties.darkTextPrimaryColor),
|
||||
icon:
|
||||
Icon(Icons.settings, color: UIColors.darkTextPrimaryColor),
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => SettingsPage()),
|
||||
),
|
||||
|
@ -506,7 +403,7 @@ class _BattlePageState extends State<BattlePage> {
|
|||
width: 180,
|
||||
margin: EdgeInsets.only(bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Properties.boardBackgroundColor,
|
||||
color: UIColors.boardBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
|
@ -523,12 +420,11 @@ class _BattlePageState extends State<BattlePage> {
|
|||
//
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: BattlePage.screenPaddingH,
|
||||
vertical: BattlePage.boardMargin,
|
||||
horizontal: GamePage.screenPaddingH,
|
||||
vertical: GamePage.boardMargin,
|
||||
),
|
||||
child: BoardWidget(
|
||||
width:
|
||||
MediaQuery.of(context).size.width - BattlePage.screenPaddingH * 2,
|
||||
child: Board(
|
||||
width: MediaQuery.of(context).size.width - GamePage.screenPaddingH * 2,
|
||||
onBoardTap: onBoardTap,
|
||||
),
|
||||
);
|
||||
|
@ -536,14 +432,14 @@ class _BattlePageState extends State<BattlePage> {
|
|||
|
||||
Widget createOperatorBar() {
|
||||
//
|
||||
final buttonStyle = TextStyle(color: Properties.primaryColor, fontSize: 20);
|
||||
final buttonStyle = TextStyle(color: UIColors.primaryColor, fontSize: 20);
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Properties.boardBackgroundColor,
|
||||
color: UIColors.boardBackgroundColor,
|
||||
),
|
||||
margin: EdgeInsets.symmetric(horizontal: BattlePage.screenPaddingH),
|
||||
margin: EdgeInsets.symmetric(horizontal: GamePage.screenPaddingH),
|
||||
padding: EdgeInsets.symmetric(vertical: 2),
|
||||
child: Row(children: <Widget>[
|
||||
Expanded(child: SizedBox()),
|
||||
|
@ -552,14 +448,14 @@ class _BattlePageState extends State<BattlePage> {
|
|||
FlatButton(
|
||||
child: Text('悔棋', style: buttonStyle),
|
||||
onPressed: () {
|
||||
Battle.shared.regret(steps: 2);
|
||||
Game.shared.regret(steps: 2);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
Expanded(child: SizedBox()),
|
||||
FlatButton(
|
||||
child: Text('分析', style: buttonStyle),
|
||||
onPressed: _analysising ? null : analysisPosition,
|
||||
onPressed: _searching ? null : analyzePosition,
|
||||
),
|
||||
Expanded(child: SizedBox()),
|
||||
]),
|
||||
|
@ -570,12 +466,12 @@ class _BattlePageState extends State<BattlePage> {
|
|||
//
|
||||
final size = MediaQuery.of(context).size;
|
||||
|
||||
final manualText = Battle.shared.position.manualText;
|
||||
final manualText = Game.shared.position.manualText;
|
||||
|
||||
if (size.height / size.width > 16 / 9) {
|
||||
return buildManualPanel(manualText);
|
||||
} else {
|
||||
return buildExpandableManaulPanel(manualText);
|
||||
return buildExpandableRecordPanel(manualText);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -583,7 +479,7 @@ class _BattlePageState extends State<BattlePage> {
|
|||
//
|
||||
final manualStyle = TextStyle(
|
||||
fontSize: 18,
|
||||
color: Properties.darkTextSecondaryColor,
|
||||
color: UIColors.darkTextSecondaryColor,
|
||||
height: 1.5,
|
||||
);
|
||||
|
||||
|
@ -595,20 +491,19 @@ class _BattlePageState extends State<BattlePage> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget buildExpandableManaulPanel(String text) {
|
||||
Widget buildExpandableRecordPanel(String text) {
|
||||
//
|
||||
final manualStyle = TextStyle(fontSize: 18, height: 1.5);
|
||||
|
||||
return Expanded(
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.expand_less, color: Properties.darkTextPrimaryColor),
|
||||
icon: Icon(Icons.expand_less, color: UIColors.darkTextPrimaryColor),
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title:
|
||||
Text('棋谱', style: TextStyle(color: Properties.primaryColor)),
|
||||
title: Text('棋谱', style: TextStyle(color: UIColors.primaryColor)),
|
||||
content:
|
||||
SingleChildScrollView(child: Text(text, style: manualStyle)),
|
||||
actions: <Widget>[
|
||||
|
@ -635,7 +530,7 @@ class _BattlePageState extends State<BattlePage> {
|
|||
final footer = buildFooter();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Properties.darkBackgroundColor,
|
||||
backgroundColor: UIColors.darkBackgroundColor,
|
||||
body: Column(children: <Widget>[header, board, operatorBar, footer]),
|
||||
);
|
||||
}
|
|
@ -18,11 +18,11 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sanmill/engine/engine.dart';
|
||||
import 'package:sanmill/main.dart';
|
||||
import 'package:sanmill/style/colors.dart';
|
||||
|
||||
import '../common/properties.dart';
|
||||
import '../engine/engine.dart';
|
||||
import '../main.dart';
|
||||
import 'battle_page.dart';
|
||||
import 'game_page.dart';
|
||||
import 'settings_page.dart';
|
||||
|
||||
class MainMenu extends StatefulWidget {
|
||||
|
@ -31,13 +31,11 @@ class MainMenu extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _MainMenuState extends State<MainMenu> with TickerProviderStateMixin {
|
||||
//
|
||||
AnimationController inController, shadowController;
|
||||
Animation inAnimation, shadowAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
//
|
||||
super.initState();
|
||||
|
||||
inController = AnimationController(
|
||||
|
@ -78,7 +76,6 @@ class _MainMenuState extends State<MainMenu> with TickerProviderStateMixin {
|
|||
}
|
||||
|
||||
navigateTo(Widget page) async {
|
||||
//
|
||||
await Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) => page));
|
||||
|
||||
|
@ -108,7 +105,7 @@ class _MainMenuState extends State<MainMenu> with TickerProviderStateMixin {
|
|||
);
|
||||
final menuItemStyle = TextStyle(
|
||||
fontSize: 28,
|
||||
color: Properties.primaryColor,
|
||||
color: UIColors.primaryColor,
|
||||
shadows: [menuItemShadow],
|
||||
);
|
||||
|
||||
|
@ -123,7 +120,7 @@ class _MainMenuState extends State<MainMenu> with TickerProviderStateMixin {
|
|||
Expanded(child: SizedBox()),
|
||||
FlatButton(
|
||||
child: Text('人机对战', style: menuItemStyle),
|
||||
onPressed: () => navigateTo(BattlePage(EngineType.Native)),
|
||||
onPressed: () => navigateTo(GamePage(EngineType.Native)),
|
||||
),
|
||||
Expanded(child: SizedBox()),
|
||||
Text('Calcitem',
|
||||
|
@ -134,7 +131,7 @@ class _MainMenuState extends State<MainMenu> with TickerProviderStateMixin {
|
|||
);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Properties.lightBackgroundColor,
|
||||
backgroundColor: UIColors.lightBackgroundColor,
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
menuItems,
|
||||
|
@ -142,7 +139,7 @@ class _MainMenuState extends State<MainMenu> with TickerProviderStateMixin {
|
|||
top: SanmillApp.StatusBarHeight,
|
||||
left: 10,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.settings, color: Properties.primaryColor),
|
||||
icon: Icon(Icons.settings, color: UIColors.primaryColor),
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => SettingsPage()),
|
||||
),
|
|
@ -20,12 +20,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:sanmill/common/config.dart';
|
||||
import 'package:sanmill/services/audios.dart';
|
||||
import 'package:sanmill/services/player.dart';
|
||||
import 'package:sanmill/style/colors.dart';
|
||||
import 'package:sanmill/style/toast.dart';
|
||||
|
||||
import '../common/config.dart';
|
||||
import '../common/properties.dart';
|
||||
import '../common/toast.dart';
|
||||
import '../services/audios.dart';
|
||||
import '../services/player.dart';
|
||||
import 'edit_page.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
|
@ -34,8 +34,7 @@ class SettingsPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> {
|
||||
//
|
||||
String _version = 'Ver 1.00';
|
||||
String _version = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -53,12 +52,12 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
changeDifficult() {
|
||||
//
|
||||
callback(int stepTime) async {
|
||||
callback(int thinkingTime) async {
|
||||
//
|
||||
Navigator.of(context).pop();
|
||||
|
||||
setState(() {
|
||||
Config.stepTime = stepTime;
|
||||
Config.thinkingTime = thinkingTime;
|
||||
});
|
||||
|
||||
Config.save();
|
||||
|
@ -71,25 +70,25 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
children: <Widget>[
|
||||
SizedBox(height: 10),
|
||||
RadioListTile(
|
||||
activeColor: Properties.primaryColor,
|
||||
activeColor: UIColors.primaryColor,
|
||||
title: Text('初级'),
|
||||
groupValue: Config.stepTime,
|
||||
groupValue: Config.thinkingTime,
|
||||
value: 5000,
|
||||
onChanged: callback,
|
||||
),
|
||||
Divider(),
|
||||
RadioListTile(
|
||||
activeColor: Properties.primaryColor,
|
||||
activeColor: UIColors.primaryColor,
|
||||
title: Text('中级'),
|
||||
groupValue: Config.stepTime,
|
||||
groupValue: Config.thinkingTime,
|
||||
value: 15000,
|
||||
onChanged: callback,
|
||||
),
|
||||
Divider(),
|
||||
RadioListTile(
|
||||
activeColor: Properties.primaryColor,
|
||||
activeColor: UIColors.primaryColor,
|
||||
title: Text('高级'),
|
||||
groupValue: Config.stepTime,
|
||||
groupValue: Config.thinkingTime,
|
||||
value: 30000,
|
||||
onChanged: callback,
|
||||
),
|
||||
|
@ -150,8 +149,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title:
|
||||
Text('关于「直棋 」', style: TextStyle(color: Properties.primaryColor)),
|
||||
title: Text('关于「直棋 」', style: TextStyle(color: UIColors.primaryColor)),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -187,11 +185,11 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
Widget build(BuildContext context) {
|
||||
//
|
||||
final TextStyle headerStyle =
|
||||
TextStyle(color: Properties.secondaryColor, fontSize: 20.0);
|
||||
final TextStyle itemStyle = TextStyle(color: Properties.primaryColor);
|
||||
TextStyle(color: UIColors.secondaryColor, fontSize: 20.0);
|
||||
final TextStyle itemStyle = TextStyle(color: UIColors.primaryColor);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Properties.lightBackgroundColor,
|
||||
backgroundColor: UIColors.lightBackgroundColor,
|
||||
appBar: AppBar(title: Text('设置')),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
@ -202,7 +200,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
Text("人机难度", style: headerStyle),
|
||||
const SizedBox(height: 10.0),
|
||||
Card(
|
||||
color: Properties.boardBackgroundColor,
|
||||
color: UIColors.boardBackgroundColor,
|
||||
elevation: 0.5,
|
||||
margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 0),
|
||||
child: Column(
|
||||
|
@ -211,11 +209,13 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
title: Text("游戏难度", style: itemStyle),
|
||||
trailing:
|
||||
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Text(Config.stepTime <= 5000
|
||||
Text(Config.thinkingTime <= 5000
|
||||
? '初级'
|
||||
: Config.stepTime <= 15000 ? '中级' : '高级'),
|
||||
: Config.thinkingTime <= 15000
|
||||
? '中级'
|
||||
: '高级'),
|
||||
Icon(Icons.keyboard_arrow_right,
|
||||
color: Properties.secondaryColor),
|
||||
color: UIColors.secondaryColor),
|
||||
]),
|
||||
onTap: changeDifficult,
|
||||
),
|
||||
|
@ -225,19 +225,19 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
const SizedBox(height: 16),
|
||||
Text("声音", style: headerStyle),
|
||||
Card(
|
||||
color: Properties.boardBackgroundColor,
|
||||
color: UIColors.boardBackgroundColor,
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SwitchListTile(
|
||||
activeColor: Properties.primaryColor,
|
||||
activeColor: UIColors.primaryColor,
|
||||
value: Config.bgmEnabled,
|
||||
title: Text("背景音乐", style: itemStyle),
|
||||
onChanged: switchMusic,
|
||||
),
|
||||
_buildDivider(),
|
||||
SwitchListTile(
|
||||
activeColor: Properties.primaryColor,
|
||||
activeColor: UIColors.primaryColor,
|
||||
value: Config.toneEnabled,
|
||||
title: Text("提示音效", style: itemStyle),
|
||||
onChanged: switchTone,
|
||||
|
@ -248,7 +248,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
const SizedBox(height: 16),
|
||||
Text("排行榜", style: headerStyle),
|
||||
Card(
|
||||
color: Properties.boardBackgroundColor,
|
||||
color: UIColors.boardBackgroundColor,
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
|
@ -258,7 +258,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Text(Player.shared.name),
|
||||
Icon(Icons.keyboard_arrow_right,
|
||||
color: Properties.secondaryColor),
|
||||
color: UIColors.secondaryColor),
|
||||
]),
|
||||
onTap: changeName,
|
||||
),
|
||||
|
@ -268,7 +268,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
const SizedBox(height: 16),
|
||||
Text("关于", style: headerStyle),
|
||||
Card(
|
||||
color: Properties.boardBackgroundColor,
|
||||
color: UIColors.boardBackgroundColor,
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
|
@ -278,7 +278,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Text(_version ?? ''),
|
||||
Icon(Icons.keyboard_arrow_right,
|
||||
color: Properties.secondaryColor),
|
||||
color: UIColors.secondaryColor),
|
||||
]),
|
||||
onTap: showAbout,
|
||||
),
|
||||
|
@ -297,7 +297,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
width: double.infinity,
|
||||
height: 1.0,
|
||||
color: Properties.lightLineColor,
|
||||
color: UIColors.lightLineColor,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -18,8 +18,7 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../common/properties.dart';
|
||||
import 'package:sanmill/style/colors.dart';
|
||||
|
||||
class WordsOnBoard extends StatelessWidget {
|
||||
//
|
||||
|
@ -45,7 +44,7 @@ class WordsOnBoard extends StatelessWidget {
|
|||
Row(children: rChildren),
|
||||
],
|
||||
),
|
||||
style: TextStyle(color: Properties.boardTipsColor),
|
||||
style: TextStyle(color: UIColors.boardTipsColor),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue