Merge remote-tracking branch 'Leptopoda/linting'

* Enabled strict linting.
* Fix all the linting errors and disabled the rules that needed to much
  restructure.
* Restructured the files.

Known Issues:

* Tap drawer menu item will get an exception when AI is thinking
Change difficulty level to 30, thinking time to 60, select AI Vs. AI and
tap Start Game, and tap on the settings in the drawer menu get the
exception:
This widget has been unmounted, so the State no longer has a context
(and should be considered defunct).

* Sound problem
when this option is enabled, it does not take effect:
  Preferences -> Sound effects -> Keep mute when taking back
The sound issue is related to some async not being
appropriately handled. This leads to the game_page setting Audios.
isTemporaryMute to false before Audios evaluated the value.
This leads to the sound being played. Some out-of-order execution.
It seems to be a thing with the code in general that Futures aren't
really awaited. This will be quite some stuff to do and check later.

* Performance
Performance testing has not yet been conducted.
This commit is contained in:
Calcitem 2021-10-11 01:19:23 +08:00
commit b04a0bb40f
No known key found for this signature in database
GPG Key ID: F2F7C29E054CFB80
56 changed files with 3459 additions and 3779 deletions

2
.gitignore vendored
View File

@ -318,7 +318,7 @@ CMakeLists.txt.user*
*.bak
# visual studio code
.vscode/
#.vscode/
# markdown temp files
*.md.Html

View File

@ -18,7 +18,7 @@
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
.vscode/
#.vscode/
# Visual Studio
.vs/

View File

@ -0,0 +1,5 @@
{
"[dart]": {
"editor.formatOnSave": true
}
}

View File

@ -0,0 +1,14 @@
include: package:lint/analysis_options.yaml
linter:
rules:
avoid_positional_boolean_parameters: false
constant_identifier_names: false
avoid_escaping_inner_quotes: false
use_build_context_synchronously: false
analyzer:
exclude:
# generated code
- lib/generated/**
- lib/l10n/**

View File

@ -19,11 +19,11 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/common/constants.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/list_item_divider.dart';
import 'package:sanmill/shared/list_item_divider.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
Map<String, Strings> languageCodeToStrings = {
"ar": ArabicStrings(),
@ -505,7 +505,7 @@ class Resources {
}
Strings get strings {
Strings? ret = languageCodeToStrings[languageCode];
final Strings? ret = languageCodeToStrings[languageCode];
if (ret == null) {
return EnglishStrings();
@ -519,8 +519,9 @@ class Resources {
}
}
setLanguage(BuildContext context, var callback) async {
var languageColumn = Column(
Future<void> setLanguage(
BuildContext context, Function(String?)? callback) async {
final languageColumn = Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RadioListTile(
@ -530,7 +531,7 @@ setLanguage(BuildContext context, var callback) async {
value: Constants.defaultLanguageCodeName,
onChanged: callback,
),
ListItemDivider(),
const ListItemDivider(),
for (var i in languageCodeToStrings.keys)
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
@ -563,7 +564,7 @@ enum Bidirectionality {
}
Bidirectionality getBidirectionality(BuildContext context) {
Locale currentLocale = Localizations.localeOf(context);
final Locale currentLocale = Localizations.localeOf(context);
if (currentLocale.languageCode == "ar" ||
currentLocale.languageCode == "fa" ||
currentLocale.languageCode == "he" ||
@ -578,8 +579,8 @@ Bidirectionality getBidirectionality(BuildContext context) {
String specialCountryAndRegion = "";
setSpecialCountryAndRegion(BuildContext context) {
Locale currentLocale = Localizations.localeOf(context);
void setSpecialCountryAndRegion(BuildContext context) {
final Locale currentLocale = Localizations.localeOf(context);
switch (currentLocale.countryCode) {
case "IR":

View File

@ -16,7 +16,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'dart:io';
import 'dart:ui';
@ -27,41 +26,39 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/screens/navigation_home_screen.dart';
import 'package:sanmill/services/audios.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/navigation_home_screen.dart';
import 'services/audios.dart';
import 'package:sanmill/shared/common/constants.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
Future<void> main() async {
var catcher = Catcher(
rootWidget: BetterFeedback(
child: SanmillApp(),
//localeOverride: Locale(Resources.of().languageCode),
),
ensureInitialized: true);
final catcher = Catcher(
rootWidget: BetterFeedback(
child: SanmillApp(),
//localeOverride: Locale(Resources.of().languageCode),
),
ensureInitialized: true,
);
String externalDirStr;
try {
Directory? externalDir = await getExternalStorageDirectory();
final Directory? externalDir = await getExternalStorageDirectory();
if (externalDir != null) {
externalDirStr = externalDir.path.toString();
externalDirStr = externalDir.path;
} else {
externalDirStr = ".";
}
} catch (e) {
print(e);
debugPrint(e.toString());
externalDirStr = ".";
}
String path = externalDirStr + "/" + Constants.crashLogsFileName;
print("[env] ExternalStorageDirectory: " + externalDirStr);
String recipients = Constants.recipients;
final String path = "$externalDirStr/${Constants.crashLogsFileName}";
debugPrint("[env] ExternalStorageDirectory: $externalDirStr");
final String recipients = Constants.recipients;
CatcherOptions debugOptions =
CatcherOptions(PageReportMode(showStackTrace: true), [
final CatcherOptions debugOptions = CatcherOptions(PageReportMode(), [
ConsoleHandler(),
FileHandler(File(path), printLogs: true),
EmailManualHandler([recipients], printLogs: true)
@ -71,14 +68,12 @@ Future<void> main() async {
/// Release configuration.
/// Same as above, but once user accepts dialog,
/// user will be prompted to send email with crash to support.
CatcherOptions releaseOptions =
CatcherOptions(PageReportMode(showStackTrace: true), [
final CatcherOptions releaseOptions = CatcherOptions(PageReportMode(), [
FileHandler(File(path), printLogs: true),
EmailManualHandler([recipients], printLogs: true)
]);
CatcherOptions profileOptions =
CatcherOptions(PageReportMode(showStackTrace: true), [
final CatcherOptions profileOptions = CatcherOptions(PageReportMode(), [
ConsoleHandler(),
FileHandler(File(path), printLogs: true),
EmailManualHandler([recipients], printLogs: true)
@ -91,16 +86,16 @@ Future<void> main() async {
profileConfig: profileOptions,
);
print(window.physicalSize);
print(Constants.windowAspectRatio);
debugPrint(window.physicalSize.toString());
debugPrint(Constants.windowAspectRatio.toString());
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown],
);
if (Platform.isAndroid && isLargeScreen()) {
if (Platform.isAndroid && isLargeScreen) {
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.dark,
@ -110,34 +105,20 @@ Future<void> main() async {
);
}
if (isSmallScreen()) {
SystemChrome.setEnabledSystemUIOverlays([]);
if (isSmallScreen) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
}
}
RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
final globalScaffoldKey = GlobalKey<ScaffoldState>();
class SanmillApp extends StatefulWidget {
@override
_SanmillAppState createState() => _SanmillAppState();
}
class _SanmillAppState extends State<SanmillApp> {
@override
void initState() {
super.initState();
if (Platform.isWindows) {
print("[audio] Audio Player is not support Windows.");
return;
} else {
Audios.loadSounds();
}
}
class SanmillApp extends StatelessWidget {
final globalScaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
Audios.loadSounds();
setSpecialCountryAndRegion(context);
return MaterialApp(
@ -146,7 +127,7 @@ class _SanmillAppState extends State<SanmillApp> {
navigatorKey: Catcher.navigatorKey,
key: globalScaffoldKey,
navigatorObservers: [routeObserver],
localizationsDelegates: [
localizationsDelegates: const [
// ... app-specific localization delegate[s] here
S.delegate,
GlobalMaterialLocalizations.delegate,
@ -159,10 +140,10 @@ class _SanmillAppState extends State<SanmillApp> {
debugShowCheckedModeBanner: false,
home: Scaffold(
body: DoubleBackToCloseApp(
child: NavigationHomeScreen(),
snackBar: SnackBar(
content: Text(Resources.of().strings.tapBackAgainToLeave),
),
child: NavigationHomeScreen(),
),
),
/*

View File

@ -16,63 +16,55 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:sanmill/common/config.dart';
import 'package:sanmill/engine/engine.dart';
import 'position.dart';
import 'types.dart';
import 'package:flutter/foundation.dart';
import 'package:sanmill/mill/position.dart';
import 'package:sanmill/mill/types.dart';
import 'package:sanmill/services/engine/engine.dart';
import 'package:sanmill/shared/common/config.dart';
enum PlayerType { human, AI }
Map<String, bool> isAi = {PieceColor.white: false, PieceColor.black: true};
// TODO: add constructor
Game gameInstance = Game();
class Game {
static Game? _instance;
final String tag = "[game]";
static get instance {
_instance ??= Game();
return _instance;
}
init() {
void init() {
_position = Position();
_focusIndex = _blurIndex = invalidIndex;
focusIndex = blurIndex = invalidIndex;
}
start() {
void start() {
position.reset();
setWhoIsAi(engineType);
}
newGame() {
void newGame() {
position.phase = Phase.ready;
start();
position.init();
_focusIndex = _blurIndex = invalidIndex;
focusIndex = blurIndex = invalidIndex;
moveHistory = [""];
sideToMove = PieceColor.white;
}
String sideToMove = PieceColor.white;
bool? isAiToMove() {
return isAi[sideToMove];
bool get isAiToMove {
assert(sideToMove == PieceColor.white || sideToMove == PieceColor.black);
return isAi[sideToMove]!;
}
List<String> moveHistory = [""];
Position _position = Position();
get position => _position;
Position get position => _position;
int _focusIndex = invalidIndex;
int _blurIndex = invalidIndex;
get focusIndex => _focusIndex;
set focusIndex(index) => _focusIndex = index;
get blurIndex => _blurIndex;
set blurIndex(index) => _blurIndex = index;
int focusIndex = invalidIndex;
int blurIndex = invalidIndex;
Map<String, bool> isSearching = {
PieceColor.white: false,
@ -80,8 +72,10 @@ class Game {
};
bool aiIsSearching() {
print("$tag White is searching? ${isSearching[PieceColor.white]}\n"
"$tag Black is searching? ${isSearching[PieceColor.black]}\n");
debugPrint(
"$tag White is searching? ${isSearching[PieceColor.white]}\n"
"$tag Black is searching? ${isSearching[PieceColor.black]}\n",
);
return isSearching[PieceColor.white] == true ||
isSearching[PieceColor.black] == true;
@ -110,13 +104,15 @@ class Game {
break;
}
print("$tag White is AI? ${isAi[PieceColor.white]}\n"
"$tag Black is AI? ${isAi[PieceColor.black]}\n");
debugPrint(
"$tag White is AI? ${isAi[PieceColor.white]}\n"
"$tag Black is AI? ${isAi[PieceColor.black]}\n",
);
}
select(int pos) {
_focusIndex = pos;
_blurIndex = invalidIndex;
void select(int pos) {
focusIndex = pos;
blurIndex = invalidIndex;
}
bool doMove(String move) {
@ -124,7 +120,7 @@ class Game {
start();
}
print("$tag AI do move: $move");
debugPrint("$tag AI do move: $move");
if (!position.doMove(move)) {
return false;
@ -132,50 +128,31 @@ class Game {
moveHistory.add(move);
sideToMove = position.sideToMove() ?? PieceColor.nobody;
sideToMove = position.sideToMove;
printStat();
return true;
}
printStat() {
void printStat() {
double whiteWinRate = 0;
double blackWinRate = 0;
double drawRate = 0;
int total = position.score[PieceColor.white] +
position.score[PieceColor.black] +
position.score[PieceColor.draw] ??
0;
final int total = position.score[PieceColor.white]! +
position.score[PieceColor.black]! +
position.score[PieceColor.draw]!;
if (total == 0) {
whiteWinRate = 0;
blackWinRate = 0;
drawRate = 0;
} else {
whiteWinRate = position.score[PieceColor.white] * 100 / total ?? 0;
blackWinRate = position.score[PieceColor.black] * 100 / total ?? 0;
drawRate = position.score[PieceColor.draw] * 100 / total ?? 0;
if (total != 0) {
whiteWinRate = position.score[PieceColor.white]! * 100 / total;
blackWinRate = position.score[PieceColor.black]! * 100 / total;
drawRate = position.score[PieceColor.draw]! * 100 / total;
}
String scoreInfo = "Score: " +
position.score[PieceColor.white].toString() +
" : " +
position.score[PieceColor.black].toString() +
" : " +
position.score[PieceColor.draw].toString() +
"\ttotal: " +
total.toString() +
"\n" +
whiteWinRate.toString() +
"% : " +
blackWinRate.toString() +
"% : " +
drawRate.toString() +
"%" +
"\n";
final String scoreInfo =
"Score: ${position.score[PieceColor.white]} : ${position.score[PieceColor.black]} : ${position.score[PieceColor.draw]}\ttotal: $total\n$whiteWinRate% : $blackWinRate% : $drawRate%\n";
print("$tag $scoreInfo");
debugPrint("$tag $scoreInfo");
}
}

View File

@ -1,4 +1,4 @@
/*
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
@ -16,10 +16,12 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'position.dart';
import 'rule.dart';
import 'package:sanmill/mill/position.dart';
import 'package:sanmill/mill/rule.dart';
class Mills {
const Mills._();
static void adjacentSquaresInit() {
// Note: Not follow order of MoveDirection array
const adjacentSquares = [
@ -31,7 +33,6 @@ class Mills {
/* 5 */ [0, 0, 0, 0],
/* 6 */ [0, 0, 0, 0],
/* 7 */ [0, 0, 0, 0],
/* 8 */ [16, 9, 15, 0],
/* 9 */ [10, 8, 0, 0],
/* 10 */ [18, 11, 9, 0],
@ -40,7 +41,6 @@ class Mills {
/* 13 */ [14, 12, 0, 0],
/* 14 */ [22, 15, 13, 0],
/* 15 */ [8, 14, 0, 0],
/* 16 */ [8, 24, 17, 23],
/* 17 */ [18, 16, 0, 0],
/* 18 */ [10, 26, 19, 17],
@ -49,7 +49,6 @@ class Mills {
/* 21 */ [22, 20, 0, 0],
/* 22 */ [14, 30, 23, 21],
/* 23 */ [16, 22, 0, 0],
/* 24 */ [16, 25, 31, 0],
/* 25 */ [26, 24, 0, 0],
/* 26 */ [18, 27, 25, 0],
@ -58,7 +57,6 @@ class Mills {
/* 29 */ [30, 28, 0, 0],
/* 30 */ [22, 31, 29, 0],
/* 31 */ [24, 30, 0, 0],
/* 32 */ [0, 0, 0, 0],
/* 33 */ [0, 0, 0, 0],
/* 34 */ [0, 0, 0, 0],
@ -78,7 +76,6 @@ class Mills {
/* 5 */ [0, 0, 0, 0],
/* 6 */ [0, 0, 0, 0],
/* 7 */ [0, 0, 0, 0],
/* 8 */ [9, 15, 16, 0],
/* 9 */ [17, 8, 10, 0],
/* 10 */ [9, 11, 18, 0],
@ -87,7 +84,6 @@ class Mills {
/* 13 */ [21, 12, 14, 0],
/* 14 */ [13, 15, 22, 0],
/* 15 */ [23, 8, 14, 0],
/* 16 */ [17, 23, 8, 24],
/* 17 */ [9, 25, 16, 18],
/* 18 */ [17, 19, 10, 26],
@ -96,7 +92,6 @@ class Mills {
/* 21 */ [13, 29, 20, 22],
/* 22 */ [21, 23, 14, 30],
/* 23 */ [15, 31, 16, 22],
/* 24 */ [25, 31, 16, 0],
/* 25 */ [17, 24, 26, 0],
/* 26 */ [25, 27, 18, 0],
@ -105,7 +100,6 @@ class Mills {
/* 29 */ [21, 28, 30, 0],
/* 30 */ [29, 31, 22, 0],
/* 31 */ [23, 24, 30, 0],
/* 32 */ [0, 0, 0, 0],
/* 33 */ [0, 0, 0, 0],
/* 34 */ [0, 0, 0, 0],
@ -165,7 +159,6 @@ class Mills {
[0, 0],
[0, 0]
],
/* 8 */ [
[16, 24],
[9, 15],
@ -206,7 +199,6 @@ class Mills {
[13, 14],
[8, 9]
],
/* 16 */ [
[8, 24],
[17, 23],
@ -247,7 +239,6 @@ class Mills {
[21, 22],
[16, 17]
],
/* 24 */ [
[8, 16],
[25, 31],
@ -288,7 +279,6 @@ class Mills {
[29, 30],
[24, 25]
],
/* 32 */ [
[0, 0],
[0, 0],
@ -372,7 +362,6 @@ class Mills {
[0, 0],
[0, 0]
],
/* 8 */ [
[16, 24],
[9, 15],
@ -413,7 +402,6 @@ class Mills {
[13, 14],
[8, 9]
],
/* 16 */ [
[8, 24],
[17, 23],
@ -454,7 +442,6 @@ class Mills {
[21, 22],
[16, 17]
],
/* 24 */ [
[8, 16],
[25, 31],
@ -495,7 +482,6 @@ class Mills {
[29, 30],
[24, 25]
],
/* 32 */ [
[0, 0],
[0, 0],

View File

@ -16,16 +16,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:sanmill/engine/engine.dart';
import 'package:flutter/foundation.dart';
import 'package:sanmill/mill/game.dart';
import 'package:sanmill/mill/mills.dart';
import 'package:sanmill/mill/recorder.dart';
import 'package:sanmill/mill/rule.dart';
import 'package:sanmill/mill/types.dart';
import 'package:sanmill/mill/zobrist.dart';
import 'package:sanmill/services/audios.dart';
import 'mills.dart';
import 'types.dart';
import 'zobrist.dart';
import 'package:sanmill/services/engine/engine.dart';
List<int> posKeyHistory = [];
@ -81,17 +80,25 @@ class Position {
String? record;
static late var millTable;
static late var adjacentSquares;
static late List<List<List<int>>> millTable;
static late List<List<int>> adjacentSquares;
late Move move;
Position() {
init();
}
Position.clone(Position other) {
_grid = [];
other._grid.forEach((piece) => _grid.add(piece));
for (final piece in other._grid) {
_grid.add(piece);
}
board = [];
other.board.forEach((piece) => board.add(piece));
for (final piece in other.board) {
board.add(piece);
}
recorder = other.recorder;
@ -124,11 +131,9 @@ class Position {
bool empty(int sq) => pieceOn(sq) == Piece.noPiece;
String sideToMove() => _sideToMove;
String get sideToMove => _sideToMove;
String movedPiece(int move) {
return pieceOn(fromSq(move));
}
String movedPiece(int move) => pieceOn(fromSq(move));
bool movePiece(int from, int to) {
if (selectPiece(from) == 0) {
@ -138,7 +143,7 @@ class Position {
return false;
}
init() {
void init() {
for (var i = 0; i < _grid.length; i++) {
_grid[i] = Piece.noPiece;
}
@ -155,95 +160,83 @@ class Position {
recorder = GameRecorder(lastPositionWithRemove: fen());
}
Position() {
init();
}
/// fen() returns a FEN representation of the position.
String fen() {
var ss = '';
final buffer = StringBuffer();
// Piece placement data
for (var file = 1; file <= fileNumber; file++) {
for (var rank = 1; rank <= rankNumber; rank++) {
final piece = pieceOnGrid(squareToIndex[makeSquare(file, rank)]!);
ss += piece;
buffer.write(piece);
}
if (file == 3)
ss += ' ';
else
ss += '/';
if (file == 3) {
buffer.write(' ');
} else {
buffer.write('/');
}
}
// Active color
ss += _sideToMove == PieceColor.white ? "w" : "b";
buffer.write(_sideToMove == PieceColor.white ? "w" : "b");
ss += " ";
buffer.write(" ");
// Phrase
switch (phase) {
case Phase.none:
ss += "n";
buffer.write("n");
break;
case Phase.ready:
ss += "r";
buffer.write("r");
break;
case Phase.placing:
ss += "p";
buffer.write("p");
break;
case Phase.moving:
ss += "m";
buffer.write("m");
break;
case Phase.gameOver:
ss += "o";
buffer.write("o");
break;
default:
ss += "?";
buffer.write("?");
break;
}
ss += " ";
buffer.write(" ");
// Action
switch (action) {
case Act.place:
ss += "p";
buffer.write("p");
break;
case Act.select:
ss += "s";
buffer.write("s");
break;
case Act.remove:
ss += "r";
buffer.write("r");
break;
default:
ss += "?";
buffer.write("?");
break;
}
ss += " ";
buffer.write(" ");
ss += pieceOnBoardCount[PieceColor.white].toString() +
" " +
pieceInHandCount[PieceColor.white].toString() +
" " +
pieceOnBoardCount[PieceColor.black].toString() +
" " +
pieceInHandCount[PieceColor.black].toString() +
" " +
pieceToRemoveCount.toString() +
" ";
buffer.write(
"${pieceOnBoardCount[PieceColor.white]} ${pieceInHandCount[PieceColor.white]} ${pieceOnBoardCount[PieceColor.black]} ${pieceInHandCount[PieceColor.black]} $pieceToRemoveCount ",
);
int sideIsBlack = _sideToMove == PieceColor.black ? 1 : 0;
final int sideIsBlack = _sideToMove == PieceColor.black ? 1 : 0;
ss += st.rule50.toString() +
" " +
(1 + (gamePly - sideIsBlack) ~/ 2).toString();
buffer.write("${st.rule50} ${1 + (gamePly - sideIsBlack) ~/ 2}");
//print("FEN is $ss");
//debugPrint("FEN is $ss");
return ss;
return buffer.toString();
}
/// Position::legal() tests whether a pseudo-legal move is legal
@ -251,16 +244,16 @@ class Position {
bool legal(Move move) {
if (!isOk(move.from) || !isOk(move.to)) return false;
String us = _sideToMove;
final String us = _sideToMove;
if (move.from == move.to) {
print("[position] Move $move.move from == to");
debugPrint("[position] Move $move.move from == to");
return false;
}
if (move.type == MoveType.remove) {
if (movedPiece(move.to) != us) {
print("[position] Move $move.to to != us");
debugPrint("[position] Move $move.to to != us");
return false;
}
}
@ -311,11 +304,11 @@ class Position {
bool ret = false;
Move m = Move(move);
final Move m = Move(move);
switch (m.type) {
case MoveType.remove:
ret = (removePiece(m.to) == 0);
ret = removePiece(m.to) == 0;
if (ret) {
// Reset rule 50 counter
st.rule50 = 0;
@ -349,13 +342,15 @@ class Position {
++st.pliesFromNull;
if (record != null && record!.length > "-(1,2)".length) {
if (posKeyHistory.length == 0 ||
(posKeyHistory.length > 0 &&
if (posKeyHistory.isEmpty ||
(posKeyHistory.isNotEmpty &&
st.key != posKeyHistory[posKeyHistory.length - 1])) {
posKeyHistory.add(st.key);
if (rule.threefoldRepetitionRule && hasGameCycle()) {
setGameOver(
PieceColor.draw, GameOverReason.drawReasonThreefoldRepetition);
PieceColor.draw,
GameOverReason.drawReasonThreefoldRepetition,
);
}
}
} else {
@ -376,7 +371,7 @@ class Position {
}
}
int size = ss.length;
final int size = ss.length;
for (int i = size - 1; i >= 0; i--) {
if (ss[i].move.type == MoveType.remove) {
@ -394,11 +389,11 @@ class Position {
bool hasGameCycle() {
int repetition = 0; // Note: Engine is global val
for (var i in posKeyHistory) {
for (final i in posKeyHistory) {
if (st.key == i) {
repetition++;
if (repetition == 3) {
print("[position] Has game cycle.");
debugPrint("[position] Has game cycle.");
return true;
}
}
@ -464,7 +459,7 @@ class Position {
bool putPiece(int s) {
var piece = Piece.noPiece;
var us = _sideToMove;
final us = _sideToMove;
if (phase == Phase.gameOver ||
action != Act.place ||
@ -478,7 +473,7 @@ class Position {
}
if (phase == Phase.placing) {
piece = sideToMove();
piece = sideToMove;
if (pieceInHandCount[us] != null) {
pieceInHandCount[us] = pieceInHandCount[us]! - 1;
}
@ -490,17 +485,19 @@ class Position {
_grid[squareToIndex[s]!] = piece;
board[s] = piece;
record = "(" + fileOf(s).toString() + "," + rankOf(s).toString() + ")";
record = "(${fileOf(s)},${rankOf(s)})";
updateKey(s);
currentSquare = s;
int n = millsCount(currentSquare);
final int n = millsCount(currentSquare);
if (n == 0) {
assert(pieceInHandCount[PieceColor.white]! >= 0 &&
pieceInHandCount[PieceColor.black]! >= 0);
assert(
pieceInHandCount[PieceColor.white]! >= 0 &&
pieceInHandCount[PieceColor.black]! >= 0,
);
if (pieceInHandCount[PieceColor.white] == 0 &&
pieceInHandCount[PieceColor.black] == 0) {
@ -525,7 +522,7 @@ class Position {
} else {
changeSideToMove();
}
Game.instance.focusIndex = squareToIndex[s] ?? invalidIndex;
gameInstance.focusIndex = squareToIndex[s] ?? invalidIndex;
Audios.playTone(Audios.placeSoundId);
} else {
pieceToRemoveCount = rule.mayRemoveMultiple ? n : 1;
@ -561,7 +558,7 @@ class Position {
action = Act.remove;
}
Game.instance.focusIndex = squareToIndex[s] ?? invalidIndex;
gameInstance.focusIndex = squareToIndex[s] ?? invalidIndex;
Audios.playTone(Audios.millSoundId);
}
} else if (phase == Phase.moving) {
@ -570,8 +567,7 @@ class Position {
}
// if illegal
if (pieceOnBoardCount[sideToMove()]! > rule.flyPieceCount ||
!rule.mayFly) {
if (pieceOnBoardCount[sideToMove]! > rule.flyPieceCount || !rule.mayFly) {
int md;
for (md = 0; md < moveDirectionNumber; md++) {
@ -580,21 +576,15 @@ class Position {
// not in moveTable
if (md == moveDirectionNumber) {
print(
"[position] putPiece: [$s] is not in [$currentSquare]'s move table.");
debugPrint(
"[position] putPiece: [$s] is not in [$currentSquare]'s move table.",
);
return false;
}
}
record = "(" +
fileOf(currentSquare).toString() +
"," +
rankOf(currentSquare).toString() +
")->(" +
fileOf(s).toString() +
"," +
rankOf(s).toString() +
")";
record =
"(${fileOf(currentSquare)},${rankOf(currentSquare)})->(${fileOf(s)},${rankOf(s)})";
st.rule50++;
@ -606,7 +596,7 @@ class Position {
_grid[squareToIndex[currentSquare]!] = Piece.noPiece;
currentSquare = s;
int n = millsCount(currentSquare);
final int n = millsCount(currentSquare);
// midgame
if (n == 0) {
@ -616,14 +606,14 @@ class Position {
if (checkIfGameIsOver()) {
return true;
} else {
Game.instance.focusIndex = squareToIndex[s] ?? invalidIndex;
gameInstance.focusIndex = squareToIndex[s] ?? invalidIndex;
Audios.playTone(Audios.placeSoundId);
}
} else {
pieceToRemoveCount = rule.mayRemoveMultiple ? n : 1;
updateKeyMisc();
action = Act.remove;
Game.instance.focusIndex = squareToIndex[s] ?? invalidIndex;
gameInstance.focusIndex = squareToIndex[s] ?? invalidIndex;
Audios.playTone(Audios.millSoundId);
}
} else {
@ -641,11 +631,11 @@ class Position {
if (pieceToRemoveCount <= 0) return -1;
// if piece is not their
if (!(PieceColor.opponent(sideToMove()) == board[s])) return -2;
if (!(PieceColor.opponent(sideToMove) == board[s])) return -2;
if (!rule.mayRemoveFromMillsAlways &&
potentialMillsCount(s, PieceColor.nobody) > 0 &&
!isAllInMills(PieceColor.opponent(sideToMove()))) {
!isAllInMills(PieceColor.opponent(sideToMove))) {
return -3;
}
@ -662,7 +652,7 @@ class Position {
board[s] = _grid[squareToIndex[s]!] = Piece.noPiece;
}
record = "-(" + fileOf(s).toString() + "," + rankOf(s).toString() + ")";
record = "-(${fileOf(s)},${rankOf(s)})";
st.rule50 = 0; // TODO: Need to move out?
if (pieceOnBoardCount[them] != null) {
@ -671,7 +661,7 @@ class Position {
if (pieceOnBoardCount[them]! + pieceInHandCount[them]! <
rule.piecesAtLeastCount) {
setGameOver(sideToMove(), GameOverReason.loseReasonlessThanThree);
setGameOver(sideToMove, GameOverReason.loseReasonlessThanThree);
return 0;
}
@ -720,13 +710,13 @@ class Position {
return -3;
}
if (!(board[sq] == sideToMove())) {
if (!(board[sq] == sideToMove)) {
return -4;
}
currentSquare = sq;
action = Act.place;
Game.instance.blurIndex = squareToIndex[sq];
gameInstance.blurIndex = squareToIndex[sq]!;
return 0;
}
@ -752,7 +742,7 @@ class Position {
gameOverReason = reason;
winner = w;
print("[position] Game over, $w win, because of $reason");
debugPrint("[position] Game over, $w win, because of $reason");
updateScore();
}
@ -813,7 +803,9 @@ class Position {
if (phase == Phase.moving && action == Act.select && isAllSurrounded()) {
if (rule.isLoseButNotChangeSideWhenNoWay) {
setGameOver(
PieceColor.opponent(sideToMove()), GameOverReason.loseReasonNoWay);
PieceColor.opponent(sideToMove),
GameOverReason.loseReasonNoWay,
);
return true;
} else {
changeSideToMove(); // TODO: Need?
@ -850,7 +842,7 @@ class Position {
void changeSideToMove() {
setSideToMove(PieceColor.opponent(_sideToMove));
st.key ^= Zobrist.side;
print("[position] $_sideToMove to move.");
debugPrint("[position] $_sideToMove to move.");
/*
if (phase == Phase.moving &&
@ -858,18 +850,16 @@ class Position {
isAllSurrounded() &&
!rule.mayFly &&
pieceOnBoardCount[sideToMove()]! >= rule.piecesAtLeastCount) {
print("[position] $_sideToMove is no way to go.");
debugPrint("[position] $_sideToMove is no way to go.");
changeSideToMove();
}
*/
}
int updateKey(int s) {
String pieceType = colorOn(s);
final String pieceType = colorOn(s);
st.key ^= Zobrist.psq[pieceColorIndex[pieceType]!][s];
return st.key;
return st.key ^= Zobrist.psq[pieceColorIndex[pieceType]!][s];
}
int revertKey(int s) {
@ -879,7 +869,7 @@ class Position {
int updateKeyMisc() {
st.key = st.key << Zobrist.KEY_MISC_BIT >> Zobrist.KEY_MISC_BIT;
st.key |= (pieceToRemoveCount) << (32 - Zobrist.KEY_MISC_BIT);
st.key |= pieceToRemoveCount << (32 - Zobrist.KEY_MISC_BIT);
return st.key;
}
@ -893,11 +883,12 @@ class Position {
int potentialMillsCount(int to, String c, {int from = 0}) {
int n = 0;
String locbak = Piece.noPiece;
String _c = c;
assert(0 <= from && from < sqNumber);
if (c == PieceColor.nobody) {
c = colorOn(to);
if (_c == PieceColor.nobody) {
_c = colorOn(to);
}
if (from != 0 && from >= sqBegin && from < sqEnd) {
@ -906,7 +897,8 @@ class Position {
}
for (int l = 0; l < lineDirectionNumber; l++) {
if (c == board[millTable[to][l][0]] && c == board[millTable[to][l][1]]) {
if (_c == board[millTable[to][l][0]] &&
_c == board[millTable[to][l][1]]) {
n++;
}
}
@ -920,10 +912,10 @@ class Position {
int millsCount(int s) {
int n = 0;
List<int?> idx = [0, 0, 0];
final List<int?> idx = [0, 0, 0];
int min = 0;
int? temp = 0;
String m = colorOn(s);
final String m = colorOn(s);
for (int i = 0; i < idx.length; i++) {
idx[0] = s;
@ -981,20 +973,18 @@ class Position {
}
// Can fly
if (pieceOnBoardCount[sideToMove()]! <= rule.flyPieceCount && rule.mayFly) {
if (pieceOnBoardCount[sideToMove]! <= rule.flyPieceCount && rule.mayFly) {
return false;
}
int? moveSquare;
for (int s = sqBegin; s < sqEnd; s++) {
if (!(sideToMove() == colorOn(s))) {
if (!(sideToMove == colorOn(s))) {
continue;
}
for (int d = moveDirectionBegin; d < moveDirectionNumber; d++) {
moveSquare = adjacentSquares[s][d];
if (moveSquare != 0 && board[moveSquare!] == Piece.noPiece) {
final int moveSquare = adjacentSquares[s][d];
if (moveSquare != 0 && board[moveSquare] == Piece.noPiece) {
return false;
}
}
@ -1005,15 +995,15 @@ class Position {
bool isStarSquare(int s) {
if (rule.hasDiagonalLines == true) {
return (s == 17 || s == 19 || s == 21 || s == 23);
return s == 17 || s == 19 || s == 21 || s == 23;
}
return (s == 16 || s == 18 || s == 20 || s == 22);
return s == 16 || s == 18 || s == 20 || s == 22;
}
///////////////////////////////////////////////////////////////////////////////
int getNPiecesInHand() {
int get nPiecesInHand {
pieceInHandCount[PieceColor.white] =
rule.piecesCount - pieceOnBoardCount[PieceColor.white]!;
pieceInHandCount[PieceColor.black] =
@ -1054,7 +1044,7 @@ class Position {
return -1;
}
getNPiecesInHand();
nPiecesInHand;
pieceToRemoveCount = 0;
winner = PieceColor.nobody;
@ -1071,7 +1061,7 @@ class Position {
for (int f = 1; f < fileExNumber; f++) {
for (int r = 0; r < rankNumber; r++) {
int s = f * rankNumber + r;
final int s = f * rankNumber + r;
if (board[s] == Piece.whiteStone) {
if (pieceOnBoardCount[PieceColor.white] != null) {
pieceOnBoardCount[PieceColor.white] =
@ -1101,37 +1091,37 @@ class Position {
String errString = "";
if (recorder == null) {
print("[goto] recorder is null.");
debugPrint("[goto] recorder is null.");
return "null";
}
var history = recorder!.getHistory();
final history = recorder!.history;
if (moveIndex < -1 || history.length <= moveIndex) {
print("[goto] moveIndex is out of range.");
debugPrint("[goto] moveIndex is out of range.");
return "out-of-range";
}
if (recorder!.cur == moveIndex) {
print("[goto] cur is equal to moveIndex.");
debugPrint("[goto] cur is equal to moveIndex.");
return "equal";
}
// Backup context
var engineTypeBackup = Game.instance.engineType;
final engineTypeBackup = gameInstance.engineType;
Game.instance.engineType = EngineType.humanVsHuman;
Game.instance.setWhoIsAi(EngineType.humanVsHuman);
gameInstance.engineType = EngineType.humanVsHuman;
gameInstance.setWhoIsAi(EngineType.humanVsHuman);
var historyBack = history;
final historyBack = history;
await Game.instance.newGame();
gameInstance.newGame();
if (moveIndex == -1) {
errString = "";
}
for (var i = 0; i <= moveIndex; i++) {
if (Game.instance.doMove(history[i].move) == false) {
if (gameInstance.doMove(history[i].move!) == false) {
errString = history[i].move!;
break;
}
@ -1141,9 +1131,9 @@ class Position {
}
// Restore context
Game.instance.engineType = engineTypeBackup;
Game.instance.setWhoIsAi(engineTypeBackup);
recorder!.setHistory(historyBack);
gameInstance.engineType = engineTypeBackup;
gameInstance.setWhoIsAi(engineTypeBackup);
recorder!.history = historyBack;
recorder!.cur = moveIndex;
return errString;
@ -1170,15 +1160,15 @@ class Position {
}
Future<String> stepForwardAll() async {
return _gotoHistory(recorder!.getHistory().length - 1);
return _gotoHistory(recorder!.history.length - 1);
}
String movesSinceLastRemove() {
int? i = 0;
String moves = "";
final buffer = StringBuffer();
int posAfterLastRemove = 0;
//print("recorder.movesCount = ${recorder.movesCount}");
//debugPrint("recorder.movesCount = ${recorder.movesCount}");
for (i = recorder!.movesCount - 1; i! >= 0; i--) {
//if (recorder.moveAt(i).type == MoveType.remove) break;
@ -1189,28 +1179,29 @@ class Position {
posAfterLastRemove = i + 1;
}
//print("[movesSinceLastRemove] posAfterLastRemove = $posAfterLastRemove");
//debugPrint("[movesSinceLastRemove] posAfterLastRemove = $posAfterLastRemove");
for (int i = posAfterLastRemove; i < recorder!.movesCount; i++) {
moves += " ${recorder!.moveAt(i).move}";
buffer.write(" ${recorder!.moveAt(i).move}");
}
//print("moves = $moves");
final String moves = buffer.toString();
//debugPrint("moves = $moves");
var idx = moves.indexOf('-(');
final idx = moves.indexOf('-(');
if (idx != -1) {
//print("moves[$idx] is -(");
//debugPrint("moves[$idx] is -(");
assert(false);
}
return moves.length > 0 ? moves.substring(1) : '';
return moves.isNotEmpty ? moves.substring(1) : '';
}
get moveHistoryText => recorder!.buildMoveHistoryText();
String get moveHistoryText => recorder!.buildMoveHistoryText();
get side => _sideToMove;
String get side => _sideToMove;
get lastMove => recorder!.last;
Move? get lastMove => recorder!.last;
get lastPositionWithRemove => recorder!.lastPositionWithRemove;
String? get lastPositionWithRemove => recorder!.lastPositionWithRemove;
}

View File

@ -16,34 +16,26 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:sanmill/common/config.dart';
import 'position.dart';
import 'types.dart';
import 'package:flutter/foundation.dart';
import 'package:sanmill/mill/position.dart';
import 'package:sanmill/mill/types.dart';
import 'package:sanmill/shared/common/config.dart';
// TODO
class GameRecorder {
int cur = -1;
String? lastPositionWithRemove = "";
var _history = <Move>[];
List<Move> history = <Move>[];
final tag = "[GameRecorder]";
GameRecorder({this.cur = -1, this.lastPositionWithRemove});
List<Move> getHistory() {
return _history;
}
void setHistory(List<Move> newHistory) {
_history = newHistory;
}
String wmdNotationToMoveString(String wmd) {
String move = "";
if (wmd.length == 3 && wmd[0] == "x") {
if (wmdNotationToMove[wmd.substring(1, 3)] != null) {
move = '-' + wmdNotationToMove[wmd.substring(1, 3)]!;
move = '-${wmdNotationToMove[wmd.substring(1, 3)]!}';
}
} else if (wmd.length == 2) {
if (wmdNotationToMove[wmd] != null) {
@ -52,15 +44,14 @@ class GameRecorder {
} else if (wmd.length == 5 && wmd[2] == '-') {
if (wmdNotationToMove[(wmd.substring(0, 2))] != null &&
wmdNotationToMove[(wmd.substring(3, 5))] != null) {
move = wmdNotationToMove[(wmd.substring(0, 2))]! +
'->' +
wmdNotationToMove[(wmd.substring(3, 5))]!;
move =
'${wmdNotationToMove[(wmd.substring(0, 2))]!}->${wmdNotationToMove[(wmd.substring(3, 5))]!}';
}
} else if ((wmd.length == 8 && wmd[2] == '-' && wmd[5] == 'x') ||
(wmd.length == 5 && wmd[2] == 'x')) {
print("$tag Not support parsing format oo-ooxo notation.");
debugPrint("$tag Not support parsing format oo-ooxo notation.");
} else {
print("$tag Parse notation $wmd failed.");
debugPrint("$tag Parse notation $wmd failed.");
}
return move;
@ -69,60 +60,57 @@ class GameRecorder {
String playOkNotationToMoveString(String playOk) {
String move = "";
if (playOk.length == 0) {
if (playOk.isEmpty) {
return "";
}
var iDash = playOk.indexOf('-');
var iX = playOk.indexOf('x');
final iDash = playOk.indexOf('-');
final iX = playOk.indexOf('x');
if (iDash == -1 && iX == -1) {
// 12
var val = int.parse(playOk);
final val = int.parse(playOk);
if (val >= 1 && val <= 24) {
move = playOkNotationToMove[playOk]!;
return move;
return playOkNotationToMove[playOk]!;
} else {
print("$tag Parse PlayOK notation $playOk failed.");
debugPrint("$tag Parse PlayOK notation $playOk failed.");
return "";
}
}
if (iX == 0) {
// x12
var sub = playOk.substring(1);
var val = int.parse(sub);
final sub = playOk.substring(1);
final val = int.parse(sub);
if (val >= 1 && val <= 24) {
move = "-" + playOkNotationToMove[sub]!;
return move;
return "-${playOkNotationToMove[sub]!}";
} else {
print("$tag Parse PlayOK notation $playOk failed.");
debugPrint("$tag Parse PlayOK notation $playOk failed.");
return "";
}
}
if (iDash != -1 && iX == -1) {
// 12-13
var sub1 = playOk.substring(0, iDash);
var val1 = int.parse(sub1);
final sub1 = playOk.substring(0, iDash);
final val1 = int.parse(sub1);
if (val1 >= 1 && val1 <= 24) {
move = playOkNotationToMove[sub1]!;
} else {
print("$tag Parse PlayOK notation $playOk failed.");
debugPrint("$tag Parse PlayOK notation $playOk failed.");
return "";
}
var sub2 = playOk.substring(iDash + 1);
var val2 = int.parse(sub2);
final sub2 = playOk.substring(iDash + 1);
final val2 = int.parse(sub2);
if (val2 >= 1 && val2 <= 24) {
move = move + "->" + playOkNotationToMove[sub2]!;
return move;
return "$move->${playOkNotationToMove[sub2]!}";
} else {
print("$tag Parse PlayOK notation $playOk failed.");
debugPrint("$tag Parse PlayOK notation $playOk failed.");
return "";
}
}
print("$tag Not support parsing format oo-ooxo PlayOK notation.");
debugPrint("$tag Not support parsing format oo-ooxo PlayOK notation.");
return "";
}
@ -141,7 +129,7 @@ class GameRecorder {
return true;
}
if (text.length > 0 && text[0] == '[') {
if (text.isNotEmpty && text[0] == '[') {
return true;
}
@ -186,8 +174,8 @@ class GameRecorder {
return importGoldToken(moveList);
}
List<Move> newHistory = [];
List<String> list = moveList
final List<Move> newHistory = [];
final List<String> list = moveList
.toLowerCase()
.replaceAll('\n', ' ')
.replaceAll(',', ' ')
@ -223,57 +211,57 @@ class GameRecorder {
i = i.trim();
if (int.tryParse(i) != null) {
i = i + '.';
i = '$i.';
}
if (i.length > 0 && !i.endsWith(".")) {
if (i.isNotEmpty && !i.endsWith(".")) {
if (i.length == 5 && i[2] == 'x') {
// "a1xc3"
String m1 = wmdNotationToMoveString(i.substring(0, 2));
final String m1 = wmdNotationToMoveString(i.substring(0, 2));
if (m1 != "") {
newHistory.add(Move(m1));
} else {
print("Cannot import $i");
debugPrint("Cannot import $i");
return i;
}
String m2 = wmdNotationToMoveString(i.substring(2));
final String m2 = wmdNotationToMoveString(i.substring(2));
if (m2 != "") {
newHistory.add(Move(m2));
} else {
print("Cannot import $i");
debugPrint("Cannot import $i");
return i;
}
} else if (i.length == 8 && i[2] == '-' && i[5] == 'x') {
// "a1-b2xc3"
String m1 = wmdNotationToMoveString(i.substring(0, 5));
final String m1 = wmdNotationToMoveString(i.substring(0, 5));
if (m1 != "") {
newHistory.add(Move(m1));
} else {
print("Cannot import $i");
debugPrint("Cannot import $i");
return i;
}
String m2 = wmdNotationToMoveString(i.substring(5));
final String m2 = wmdNotationToMoveString(i.substring(5));
if (m2 != "") {
newHistory.add(Move(m2));
} else {
print("Cannot import $i");
debugPrint("Cannot import $i");
return i;
}
} else {
// no x
String m = wmdNotationToMoveString(i);
final String m = wmdNotationToMoveString(i);
if (m != "") {
newHistory.add(Move(m));
} else {
print("Cannot import $i");
debugPrint("Cannot import $i");
return i;
}
}
}
}
if (newHistory.length > 0) {
setHistory(newHistory);
if (newHistory.isNotEmpty) {
history = newHistory;
}
return "";
@ -284,9 +272,9 @@ class GameRecorder {
}
String importPlayOk(String moveList) {
List<Move> newHistory = [];
final List<Move> newHistory = [];
List<String> list = moveList
final List<String> list = moveList
.replaceAll('\n', ' ')
.replaceAll(' 1/2-1/2', '')
.replaceAll(' 1-0', '')
@ -297,40 +285,40 @@ class GameRecorder {
for (var i in list) {
i = i.trim();
if (i.length > 0 &&
if (i.isNotEmpty &&
!i.endsWith(".") &&
!i.startsWith("[") &&
!i.endsWith("]")) {
var iX = i.indexOf('x');
final iX = i.indexOf('x');
if (iX == -1) {
String m = playOkNotationToMoveString(i);
final String m = playOkNotationToMoveString(i);
if (m != "") {
newHistory.add(Move(m));
} else {
print("Cannot import $i");
debugPrint("Cannot import $i");
return i;
}
} else if (iX != -1) {
String m1 = playOkNotationToMoveString(i.substring(0, iX));
final String m1 = playOkNotationToMoveString(i.substring(0, iX));
if (m1 != "") {
newHistory.add(Move(m1));
} else {
print("Cannot import $i");
debugPrint("Cannot import $i");
return i;
}
String m2 = playOkNotationToMoveString(i.substring(iX));
final String m2 = playOkNotationToMoveString(i.substring(iX));
if (m2 != "") {
newHistory.add(Move(m2));
} else {
print("Cannot import $i");
debugPrint("Cannot import $i");
return i;
}
}
}
}
if (newHistory.length > 0) {
setHistory(newHistory);
if (newHistory.isNotEmpty) {
history = newHistory;
}
return "";
@ -355,16 +343,16 @@ class GameRecorder {
}
void jumpToTail() {
cur = _history.length - 1;
cur = history.length - 1;
}
void clear() {
_history.clear();
history.clear();
cur = 0;
}
bool isClean() {
return cur == _history.length - 1;
return cur == history.length - 1;
}
void prune() {
@ -372,19 +360,19 @@ class GameRecorder {
return;
}
_history.removeRange(cur + 1, _history.length);
history.removeRange(cur + 1, history.length);
}
void moveIn(Move move, Position position) {
if (_history.length > 0) {
if (_history[_history.length - 1].move == move.move) {
if (history.isNotEmpty) {
if (history[history.length - 1].move == move.move) {
//assert(false);
// TODO: WAR
return;
}
}
_history.add(move);
history.add(move);
cur++;
if (move.type == MoveType.remove) {
@ -393,22 +381,22 @@ class GameRecorder {
}
Move? removeLast() {
if (_history.isEmpty) return null;
return _history.removeLast();
if (history.isEmpty) return null;
return history.removeLast();
}
get last => _history.isEmpty ? null : _history.last;
Move? get last => history.isEmpty ? null : history.last;
Move moveAt(int index) => _history[index];
Move moveAt(int index) => history[index];
get movesCount => _history.length;
int get movesCount => history.length;
get lastMove => movesCount == 0 ? null : moveAt(movesCount - 1);
Move? get lastMove => movesCount == 0 ? null : moveAt(movesCount - 1);
get lastEffectiveMove => cur == -1 ? null : moveAt(cur);
Move? get lastEffectiveMove => cur == -1 ? null : moveAt(cur);
String buildMoveHistoryText({cols = 2}) {
if (_history.length == 0) {
String buildMoveHistoryText({int cols = 2}) {
if (history.isEmpty) {
return '';
}
@ -421,21 +409,21 @@ class GameRecorder {
if (k % cols == 1) {
num = "${(k + 1) ~/ 2}. ";
if (k < 9 * cols) {
num = " " + num + " ";
num = " $num ";
}
} else {
num = "";
}
if (i + 1 <= cur && _history[i + 1].type == MoveType.remove) {
if (i + 1 <= cur && history[i + 1].type == MoveType.remove) {
moveHistoryText +=
'$num${_history[i].notation}${_history[i + 1].notation} ';
'$num${history[i].notation}${history[i + 1].notation} ';
i++;
} else {
moveHistoryText += '$num${_history[i].notation} ';
moveHistoryText += '$num${history[i].notation} ';
}
k++;
} else {
moveHistoryText += '${i < 9 ? ' ' : ''}${i + 1}. ${_history[i].move} ';
moveHistoryText += '${i < 9 ? ' ' : ''}${i + 1}. ${history[i].move} ';
}
if (Config.standardNotationEnabled) {
@ -449,8 +437,6 @@ class GameRecorder {
moveHistoryText = "";
}
moveHistoryText = moveHistoryText.replaceAll(' \n', '\n');
return moveHistoryText;
return moveHistoryText.replaceAll(' \n', '\n');
}
}

View File

@ -24,7 +24,7 @@ class Rule {
int piecesCount = specialCountryAndRegion == "Iran" ? 12 : 9;
int flyPieceCount = 3;
int piecesAtLeastCount = 3;
bool hasDiagonalLines = specialCountryAndRegion == "Iran" ? true : false;
bool hasDiagonalLines = specialCountryAndRegion == "Iran";
bool hasBannedLocations = false;
bool mayMoveInPlacingPhase = false;
bool isDefenderMoveFirst = false;

View File

@ -16,7 +16,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
abs(value) => value > 0 ? value : -value;
import 'package:flutter/foundation.dart';
int abs(int value) => value > 0 ? value : -value;
class Move {
static const invalidMove = -1;
@ -48,7 +50,7 @@ class Move {
// Used to restore fen step counter when undoing move
String counterMarks = "";
parse() {
void parse() {
if (!legal(move)) {
throw "Error: Invalid Move: $move";
}
@ -82,7 +84,7 @@ class Move {
removed = Piece.noPiece;
} else if (move == "draw") {
// TODO
print("[TODO] Computer request draw");
debugPrint("[TODO] Computer request draw");
} else {
assert(false);
}
@ -99,8 +101,7 @@ class Move {
/// Remove: -(1,2)
/// Move: (3,1)->(2,1)
Move.set(String move) {
this.move = move;
Move.set(this.move) {
parse();
}
@ -111,7 +112,7 @@ class Move {
if (move == null || move.length > "(3,1)->(2,1)".length) return false;
String range = "0123456789(,)->";
const String range = "0123456789(,)->";
if (!(move[0] == '(' || move[0] == '-')) {
return false;
@ -146,10 +147,16 @@ class PieceColor {
static const draw = '=';
static String of(String piece) {
if (white.contains(piece)) return white;
if (black.contains(piece)) return black;
if (ban.contains(piece)) return ban;
return nobody;
switch (piece) {
case white:
return white;
case black:
return black;
case ban:
return ban;
default:
return nobody;
}
}
static bool isSameColor(String p1, String p2) => of(p1) == of(p2);
@ -190,6 +197,7 @@ enum GameOverReason {
enum PieceType { none, whiteStone, blackStone, ban, count, stone }
class Piece {
const Piece._();
static const noPiece = PieceColor.none;
static const whiteStone = PieceColor.white;
static const blackStone = PieceColor.black;
@ -264,17 +272,17 @@ int makeSquare(int file, int rank) {
}
bool isOk(int sq) {
bool ret = (sq == 0 || (sq >= sqBegin && sq < sqEnd));
final bool ret = sq == 0 || (sq >= sqBegin && sq < sqEnd);
if (ret == false) {
print("[types] $sq is not OK");
debugPrint("[types] $sq is not OK");
}
return ret; // TODO: SQ_NONE?
}
int fileOf(int sq) {
return (sq >> 3);
return sq >> 3;
}
int rankOf(int sq) {
@ -282,13 +290,11 @@ int rankOf(int sq) {
}
int fromSq(int move) {
move = abs(move);
return (move >> 8);
return abs(move) >> 8;
}
int toSq(int move) {
move = abs(move);
return (move & 0x00FF);
return abs(move) & 0x00FF;
}
int makeMove(int from, int to) {

View File

@ -17,8 +17,10 @@
*/
class Zobrist {
const Zobrist._();
static const int KEY_MISC_BIT = 2;
static var psq = [
static const List<List<int>> psq = [
[
0x4E421A,
0x3962FF,
@ -189,5 +191,5 @@ class Zobrist {
]
];
static int side = 0x201906;
static const int side = 0x201906;
}

View File

@ -22,90 +22,65 @@ import 'package:devicelocale/devicelocale.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/generated/flutter_version.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/settings_list_tile.dart';
import 'package:sanmill/screens/license_page.dart';
import 'package:sanmill/screens/oss_license_page.dart';
import 'package:sanmill/shared/common/constants.dart';
import 'package:sanmill/shared/list_item_divider.dart';
import 'package:sanmill/shared/settings/settings_list_tile.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
import 'package:url_launcher/url_launcher.dart';
import 'license_page.dart';
import 'list_item_divider.dart';
import 'oss_license_page.dart';
class AboutPage extends StatefulWidget {
@override
_AboutPageState createState() => _AboutPageState();
}
class _AboutPageState extends State<AboutPage> {
String _version = "";
class AboutPage extends StatelessWidget {
final String tag = "[about] ";
@override
void initState() {
_loadVersionInfo();
super.initState();
}
String getMode() {
late String ret;
String get mode {
if (kDebugMode) {
ret = "- debug";
return "- debug";
} else if (kProfileMode) {
ret = "- profile";
return "- profile";
} else if (kReleaseMode) {
ret = "";
return "";
} else {
ret = "-test";
return "-test";
}
return ret;
}
@override
Widget build(BuildContext context) {
String mode = getMode();
final List<Widget> _children = [
FutureBuilder<PackageInfo>(
future: PackageInfo.fromPlatform(),
builder: (_, data) {
late final String _version;
if (!data.hasData) {
_version = '';
} else {
final packageInfo = data.data!;
if (Platform.isWindows) {
_version = packageInfo.version; // TODO
return Scaffold(
backgroundColor: AppTheme.aboutPageBackgroundColor,
appBar: AppBar(
centerTitle: true,
title: Text(S.of(context).about + " " + S.of(context).appName)),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: children(context, mode),
),
} else {
_version = '${packageInfo.version} (${packageInfo.buildNumber})';
}
}
return SettingsListTile(
titleString: S.of(context).versionInfo,
subtitleString: "${Constants.projectName} $_version $mode",
onTap: () => _showVersionInfo(context, _version),
);
},
),
);
}
List<Widget> children(BuildContext context, String mode) {
return <Widget>[
SettingsListTile(
context: context,
titleString: S.of(context).versionInfo,
subtitleString: Constants.projectName + " $_version" + " " + mode,
onTap: _showVersionInfo,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).feedback,
onTap: _launchFeedback,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).eula,
onTap: () {
_launchEULA();
},
onTap: _launchEULA,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).license,
onTap: () {
Navigator.push(
@ -116,76 +91,54 @@ class _AboutPageState extends State<AboutPage> {
);
},
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).sourceCode,
onTap: () {
_launchSourceCode();
},
onTap: _launchSourceCode,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).privacyPolicy,
onTap: () {
_launchPrivacyPolicy();
},
onTap: _launchPrivacyPolicy,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).ossLicenses,
onTap: () {
_launchThirdPartyNotices();
},
onTap: () => _launchThirdPartyNotices(context),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).helpImproveTranslate,
onTap: () {
_launchHelpImproveTranslate();
},
onTap: _launchHelpImproveTranslate,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).thanks,
onTap: () {
_launchThanks();
},
onTap: _launchThanks,
),
ListItemDivider(),
];
return Scaffold(
backgroundColor: AppTheme.aboutPageBackgroundColor,
appBar: AppBar(
centerTitle: true,
title: Text("${S.of(context).about} ${S.of(context).appName}"),
),
body: ListView.separated(
itemBuilder: (_, index) => _children[index],
separatorBuilder: (_, __) => const ListItemDivider(),
itemCount: _children.length,
),
);
}
_loadVersionInfo() async {
final packageInfo = await PackageInfo.fromPlatform();
if (Platform.isWindows) {
setState(() {
_version = '${packageInfo.version}'; // TODO
});
} else {
setState(() {
_version = '${packageInfo.version} (${packageInfo.buildNumber})';
});
}
}
_launchURL(String url) async {
Future<void> _launchURL(String url) async {
await launch(url);
}
_launchFeedback() async {
Future<void> _launchFeedback() async {
String? locale = "en_US";
if (!Platform.isWindows) {
locale = await Devicelocale.currentLocale;
}
print("$tag local = $locale");
debugPrint("$tag local = $locale");
if (locale != null && locale.startsWith("zh_")) {
_launchURL(Constants.giteeIssuesURL);
} else {
@ -193,14 +146,14 @@ class _AboutPageState extends State<AboutPage> {
}
}
_launchEULA() async {
Future<void> _launchEULA() async {
String? locale = "en_US";
if (!Platform.isWindows) {
locale = await Devicelocale.currentLocale;
}
print("$tag local = $locale");
debugPrint("$tag local = $locale");
if (locale != null && locale.startsWith("zh_")) {
_launchURL(Constants.giteeEulaURL);
} else {
@ -208,14 +161,14 @@ class _AboutPageState extends State<AboutPage> {
}
}
_launchSourceCode() async {
Future<void> _launchSourceCode() async {
String? locale = "en_US";
if (!Platform.isWindows) {
locale = await Devicelocale.currentLocale;
}
print("$tag local = $locale");
debugPrint("$tag local = $locale");
if (locale != null && locale.startsWith("zh_")) {
_launchURL(Constants.giteeSourceCodeURL);
} else {
@ -223,12 +176,13 @@ class _AboutPageState extends State<AboutPage> {
}
}
_launchThirdPartyNotices() async {
Future<void> _launchThirdPartyNotices(BuildContext context) async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OssLicensesPage(),
));
context,
MaterialPageRoute(
builder: (context) => OssLicensesPage(),
),
);
/*
String? locale = "en_US";
@ -236,7 +190,7 @@ class _AboutPageState extends State<AboutPage> {
locale = await Devicelocale.currentLocale;
}
print("$tag local = $locale");
debugPrint("$tag local = $locale");
if (locale != null && locale.startsWith("zh_")) {
_launchURL(Constants.giteeThirdPartyNoticesURL);
} else {
@ -245,14 +199,14 @@ class _AboutPageState extends State<AboutPage> {
*/
}
_launchPrivacyPolicy() async {
Future<void> _launchPrivacyPolicy() async {
String? locale = "en_US";
if (!Platform.isWindows) {
locale = await Devicelocale.currentLocale;
}
print("$tag local = $locale");
debugPrint("$tag local = $locale");
if (locale != null && locale.startsWith("zh_")) {
_launchURL(Constants.giteePrivacyPolicyURL);
} else {
@ -260,14 +214,14 @@ class _AboutPageState extends State<AboutPage> {
}
}
_launchHelpImproveTranslate() async {
Future<void> _launchHelpImproveTranslate() async {
String? locale = "en_US";
if (!Platform.isWindows) {
locale = await Devicelocale.currentLocale;
}
print("$tag local = $locale");
debugPrint("$tag local = $locale");
if (locale != null && locale.startsWith("zh_")) {
_launchURL(Constants.giteeHelpImproveTranslateURL);
} else {
@ -275,14 +229,14 @@ class _AboutPageState extends State<AboutPage> {
}
}
_launchThanks() async {
Future<void> _launchThanks() async {
String? locale = "en_US";
if (!Platform.isWindows) {
locale = await Devicelocale.currentLocale;
}
print("$tag local = $locale");
debugPrint("$tag local = $locale");
if (locale != null && locale.startsWith("zh_")) {
_launchURL(Constants.giteeThanksURL);
} else {
@ -290,27 +244,39 @@ class _AboutPageState extends State<AboutPage> {
}
}
_showVersionInfo() {
void _showVersionInfo(BuildContext context, String version) {
showDialog(
context: context,
barrierDismissible: true,
builder: (context) => versionDialog(context),
builder: (_) => _VersionDialog(
version: version,
),
);
}
}
AlertDialog versionDialog(BuildContext context) {
class _VersionDialog extends StatelessWidget {
const _VersionDialog({
Key? key,
required this.version,
}) : super(key: key);
final String version;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(
S.of(context).appName,
style: TextStyle(color: AppTheme.dialogTitleColor),
style: const TextStyle(color: AppTheme.dialogTitleColor),
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(S.of(context).version + ": $_version"),
SizedBox(height: AppTheme.sizedBoxHeight),
SizedBox(height: AppTheme.sizedBoxHeight),
Text("${S.of(context).version}: $version"),
const SizedBox(height: AppTheme.sizedBoxHeight),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(
S.of(context).copyright,
style: TextStyle(
@ -322,45 +288,49 @@ class _AboutPageState extends State<AboutPage> {
actions: <Widget>[
TextButton(
child: Text(S.of(context).more),
onPressed: () => _showFlutterVersionInfo(),
onPressed: () => _showFlutterVersionInfo(context),
),
TextButton(
child: Text(S.of(context).ok),
onPressed: () => Navigator.of(context).pop(),
onPressed: () => Navigator.pop(context),
),
],
);
}
_showFlutterVersionInfo() {
Navigator.of(context).pop();
void _showFlutterVersionInfo(BuildContext context) {
Navigator.pop(context);
showDialog(
context: context,
barrierDismissible: true,
builder: (context) => flutterVersionDialog(context),
builder: (context) => _flutterVersionDialog(context),
);
}
AlertDialog flutterVersionDialog(BuildContext context) {
AlertDialog _flutterVersionDialog(BuildContext context) {
return AlertDialog(
title: Text(
S.of(context).more,
style: TextStyle(color: AppTheme.dialogTitleColor),
style: const TextStyle(color: AppTheme.dialogTitleColor),
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"${flutterVersion.toString().replaceAll('{', '').replaceAll('}', '').replaceAll(', ', '\n')}",
flutterVersion
.toString()
.replaceAll('{', '')
.replaceAll('}', '')
.replaceAll(', ', '\n'),
),
],
),
actions: <Widget>[
TextButton(
child: Text(S.of(context).ok),
onPressed: () => Navigator.of(context).pop(),
onPressed: () => Navigator.pop(context),
),
],
);

View File

@ -0,0 +1,54 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/shared/common/constants.dart';
class EnvironmentVariablesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: rootBundle.loadString(Constants.environmentVariablesFilename),
builder: (context, data) {
late final String _data;
if (!data.hasData) {
_data = 'Nothing to show';
} else {
_data = data.data!;
}
return Scaffold(
appBar: AppBar(
title: Text(S.of(context).environmentVariables),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Text(
_data,
style: const TextStyle(fontFamily: 'Monospace', fontSize: 12),
textAlign: TextAlign.left,
),
),
);
},
);
}
}

View File

@ -16,22 +16,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/mill/game.dart';
import 'package:sanmill/mill/types.dart';
import 'package:sanmill/painting/board_painter.dart';
import 'package:sanmill/painting/pieces_painter.dart';
import 'package:sanmill/style/app_theme.dart';
part of 'package:sanmill/screens/game_page/game_page.dart';
typedef BoardTapCallback = dynamic Function(int index);
class Board extends StatelessWidget {
final double width;
final double height;
final Function(BuildContext, int) onBoardTap;
final animationValue;
List<String> squareDesc = [];
final BoardTapCallback onBoardTap;
final double animationValue;
final List<String> squareDesc = [];
final String tag = "[board]";
Board({
@ -42,44 +36,39 @@ class Board extends StatelessWidget {
@override
Widget build(BuildContext context) {
var padding = AppTheme.boardPadding;
final padding = AppTheme.boardPadding;
buildSquareDescription(context);
var container = Container(
margin: EdgeInsets.symmetric(
vertical: 0,
horizontal: 0,
final grid = GridView(
scrollDirection: Axis.horizontal,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7,
),
child: GridView(
scrollDirection: Axis.horizontal,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7,
),
children: List.generate(7 * 7, (index) {
return Center(
child: Text(
squareDesc[index],
style: TextStyle(
fontSize: Config.fontSize,
color: Config.developerMode ? Colors.red : Colors.transparent,
),
children: List.generate(
7 * 7,
(index) => Center(
child: Text(
squareDesc[index],
style: TextStyle(
fontSize: Config.fontSize,
color: Config.developerMode ? Colors.red : Colors.transparent,
),
);
}),
),
),
),
);
var customPaint = CustomPaint(
final customPaint = CustomPaint(
painter: BoardPainter(width: width),
foregroundPainter: PiecesPainter(
width: width,
position: Game.instance.position,
focusIndex: Game.instance.focusIndex,
blurIndex: Game.instance.blurIndex,
position: gameInstance.position,
focusIndex: gameInstance.focusIndex,
blurIndex: gameInstance.blurIndex,
animationValue: animationValue,
),
child: container,
child: grid,
);
final boardContainer = Container(
@ -101,37 +90,37 @@ class Board extends StatelessWidget {
*/
child: boardContainer,
onTapUp: (d) {
final gridWidth = (width - padding * 2);
final gridWidth = width - padding * 2;
final squareWidth = gridWidth / 7;
final dx = d.localPosition.dx;
final dy = d.localPosition.dy;
final column = (dx - padding) ~/ squareWidth;
if (column < 0 || column > 6) {
print("$tag Tap on column $column (ignored).");
debugPrint("$tag Tap on column $column (ignored).");
return;
}
final row = (dy - padding) ~/ squareWidth;
if (row < 0 || row > 6) {
print("$tag Tap on row $row (ignored).");
debugPrint("$tag Tap on row $row (ignored).");
return;
}
final index = row * 7 + column;
print("$tag Tap on ($row, $column) <$index>");
debugPrint("$tag Tap on ($row, $column) <$index>");
onBoardTap(context, index);
onBoardTap(index);
},
);
}
void buildSquareDescription(BuildContext context) {
List<String> coordinates = [];
List<String> pieceDesc = [];
final List<String> coordinates = [];
final List<String> pieceDesc = [];
var map = [
const map = [
/* 1 */
1,
8,
@ -190,7 +179,7 @@ class Board extends StatelessWidget {
49
];
var checkPoints = [
const checkPoints = [
/* 1 */
1,
0,
@ -249,17 +238,18 @@ class Board extends StatelessWidget {
1
];
bool ltr = getBidirectionality(context) == Bidirectionality.leftToRight;
final bool ltr =
getBidirectionality(context) == Bidirectionality.leftToRight;
if (ltr) {
for (var file in ['a', 'b', 'c', 'd', 'e', 'f', 'g']) {
for (var rank in ['7', '6', '5', '4', '3', '2', '1']) {
for (final file in ['a', 'b', 'c', 'd', 'e', 'f', 'g']) {
for (final rank in ['7', '6', '5', '4', '3', '2', '1']) {
coordinates.add("$file$rank");
}
}
} else {
for (var file in ['g', 'f', 'e', 'd', 'c', 'b', 'a']) {
for (var rank in ['7', '6', '5', '4', '3', '2', '1']) {
for (final file in ['g', 'f', 'e', 'd', 'c', 'b', 'a']) {
for (final rank in ['7', '6', '5', '4', '3', '2', '1']) {
coordinates.add("$file$rank");
}
}
@ -268,25 +258,38 @@ class Board extends StatelessWidget {
for (var i = 0; i < 7 * 7; i++) {
if (checkPoints[i] == 0) {
pieceDesc.add(S.of(context).noPoint);
} else if (Game.instance.position.pieceOnGrid(i) == PieceColor.white) {
pieceDesc.add(S.of(context).whitePiece);
} else if (Game.instance.position.pieceOnGrid(i) == PieceColor.black) {
pieceDesc.add(S.of(context).blackPiece);
} else if (Game.instance.position.pieceOnGrid(i) == PieceColor.ban) {
pieceDesc.add(S.of(context).banPoint);
} else if (Game.instance.position.pieceOnGrid(i) == PieceColor.none) {
pieceDesc.add(S.of(context).emptyPoint);
} else {
switch (gameInstance.position.pieceOnGrid(i)) {
case PieceColor.white:
pieceDesc.add(S.of(context).whitePiece);
break;
case PieceColor.black:
pieceDesc.add(S.of(context).blackPiece);
break;
case PieceColor.ban:
pieceDesc.add(S.of(context).banPoint);
break;
case PieceColor.none:
pieceDesc.add(S.of(context).emptyPoint);
break;
default:
}
}
}
squareDesc.clear();
for (var i = 0; i < 7 * 7; i++) {
var desc = pieceDesc[map[i] - 1];
final desc = pieceDesc[map[i] - 1];
if (desc == S.of(context).emptyPoint) {
squareDesc.add(coordinates[i] + ": " + desc);
squareDesc.add("${coordinates[i]}: $desc");
} else {
squareDesc.add(desc + ": " + coordinates[i]);
squareDesc.add("$desc: ${coordinates[i]}");
}
}
}

View File

@ -0,0 +1,44 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
part of 'package:sanmill/screens/game_page/game_page.dart';
class GamePageToolBar extends StatelessWidget {
final List<Widget> children;
const GamePageToolBar({
Key? key,
required this.children,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Color(Config.navigationToolbarBackgroundColor),
),
margin: const EdgeInsets.symmetric(vertical: 0.5),
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
textDirection: TextDirection.ltr,
children: children,
),
);
}
}

View File

@ -0,0 +1,556 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/screens/env_page.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/common/settings.dart';
import 'package:sanmill/shared/dialog.dart';
import 'package:sanmill/shared/settings/settings_card.dart';
import 'package:sanmill/shared/settings/settings_list_tile.dart';
import 'package:sanmill/shared/settings/settings_switch_list_tile.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
class Developer {
const Developer._();
static bool developerModeEnabled = false;
}
class GameSettingsPage extends StatefulWidget {
@override
_GameSettingsPageState createState() => _GameSettingsPageState();
}
class _GameSettingsPageState extends State<GameSettingsPage> {
Color pickerColor = const Color(0xFF808080);
Color currentColor = const Color(0xFF808080);
late StreamController<int> _events;
List<String> algorithmNames = ['Alpha-Beta', 'PVS', 'MTD(f)'];
final String tag = "[game_settings_page]";
@override
void initState() {
super.initState();
_events = StreamController<int>.broadcast();
_events.add(10);
}
Future<void> _restore() async {
final settings = await Settings.instance();
await settings.restore();
}
SliderTheme _skillLevelSliderTheme(BuildContext context, Function setState) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).skillLevel,
child: Slider(
value: Config.skillLevel.toDouble(),
min: 1,
max: 30,
divisions: 29,
label: Config.skillLevel.toString(),
onChanged: (value) => setState(() {
debugPrint("[config] Slider value: $value");
Config.skillLevel = value.toInt();
Config.save();
}),
),
),
);
}
SliderTheme _moveTimeSliderTheme(BuildContext context, Function setState) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).moveTime,
child: Slider(
value: Config.moveTime.toDouble(),
max: 60,
divisions: 60,
label: Config.moveTime.toString(),
onChanged: (value) => setState(() {
debugPrint("[config] Slider value: $value");
Config.moveTime = value.toInt();
Config.save();
}),
),
),
);
}
// Restore
Future<void> restoreFactoryDefaultSettings() async {
Future<void> confirm() async {
Navigator.pop(context);
if (Platform.isAndroid) {
showCountdownDialog(context, 10, _events, _restore);
} else {
_restore();
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(S.of(context).exitAppManually)),
);
}
}
void cancel() => Navigator.pop(context);
var prompt = "";
if (Platform.isAndroid) {
prompt = S.of(context).exitApp;
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
S.of(context).restore,
style: TextStyle(
color: AppTheme.dialogTitleColor,
fontSize: Config.fontSize + 4,
),
),
content: SingleChildScrollView(
child: Text(
"${S.of(context).restoreDefaultSettings}?\n$prompt",
style: TextStyle(
fontSize: Config.fontSize,
),
),
),
actions: <Widget>[
TextButton(
onPressed: confirm,
child: Text(
S.of(context).ok,
style: TextStyle(
fontSize: Config.fontSize,
),
),
),
TextButton(
onPressed: cancel,
child: Text(
S.of(context).cancel,
style: TextStyle(
fontSize: Config.fontSize,
),
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.lightBackgroundColor,
appBar: AppBar(
centerTitle: true,
title: Text(S.of(context).preferences),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children(context),
),
),
);
}
List<Widget> children(BuildContext context) {
return <Widget>[
Text(S.of(context).whoMovesFirst, style: AppTheme.settingsHeaderStyle),
SettingsCard(
children: <Widget>[
SettingsSwitchListTile(
value: !Config.aiMovesFirst,
onChanged: setWhoMovesFirst,
titleString:
Config.aiMovesFirst ? S.of(context).ai : S.of(context).human,
),
],
),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).difficulty, style: AppTheme.settingsHeaderStyle),
SettingsCard(
children: <Widget>[
SettingsListTile(
titleString: S.of(context).skillLevel,
//trailingString: "L" + Config.skillLevel.toString(),
onTap: setSkillLevel,
),
SettingsListTile(
titleString: S.of(context).moveTime,
onTap: setMoveTime,
),
],
),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).aisPlayStyle, style: AppTheme.settingsHeaderStyle),
SettingsCard(
children: <Widget>[
SettingsListTile(
titleString: S.of(context).algorithm,
trailingString: algorithmNames[Config.algorithm],
onTap: setAlgorithm,
),
SettingsSwitchListTile(
value: Config.drawOnHumanExperience,
onChanged: setDrawOnHumanExperience,
titleString: S.of(context).drawOnHumanExperience,
),
SettingsSwitchListTile(
value: Config.considerMobility,
onChanged: setConsiderMobility,
titleString: S.of(context).considerMobility,
),
SettingsSwitchListTile(
value: Config.aiIsLazy,
onChanged: setAiIsLazy,
titleString: S.of(context).passive,
),
SettingsSwitchListTile(
value: Config.shufflingEnabled,
onChanged: setShufflingEnabled,
titleString: S.of(context).shufflingEnabled,
),
],
),
if (!Platform.isWindows) const SizedBox(height: AppTheme.sizedBoxHeight),
if (!Platform.isWindows)
Text(S.of(context).playSounds, style: AppTheme.settingsHeaderStyle),
if (!Platform.isWindows)
SettingsCard(
children: <Widget>[
SettingsSwitchListTile(
value: Config.toneEnabled,
onChanged: setTone,
titleString: S.of(context).playSoundsInTheGame,
),
SettingsSwitchListTile(
value: Config.keepMuteWhenTakingBack,
onChanged: setKeepMuteWhenTakingBack,
titleString: S.of(context).keepMuteWhenTakingBack,
),
],
),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).accessibility, style: AppTheme.settingsHeaderStyle),
SettingsCard(
children: <Widget>[
SettingsSwitchListTile(
value: Config.screenReaderSupport,
onChanged: setScreenReaderSupport,
titleString: S.of(context).screenReaderSupport,
),
],
),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).restore, style: AppTheme.settingsHeaderStyle),
SettingsCard(
children: <Widget>[
SettingsListTile(
titleString: S.of(context).restoreDefaultSettings,
onTap: restoreFactoryDefaultSettings,
),
],
),
const SizedBox(height: AppTheme.sizedBoxHeight),
if (Developer.developerModeEnabled)
Text(
S.of(context).forDevelopers,
style: AppTheme.settingsHeaderStyle,
),
if (Developer.developerModeEnabled)
SettingsCard(
children: <Widget>[
SettingsSwitchListTile(
value: Config.developerMode,
onChanged: setDeveloperMode,
titleString: S.of(context).developerMode,
),
SettingsSwitchListTile(
value: Config.experimentsEnabled,
onChanged: setExperimentsEnabled,
titleString: S.of(context).experiments,
),
SettingsSwitchListTile(
value: Config.isAutoRestart,
onChanged: setIsAutoRestart,
titleString: S.of(context).isAutoRestart,
),
SettingsListTile(
titleString: S.of(context).environmentVariables,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EnvironmentVariablesPage(),
),
);
},
),
],
),
];
}
Future<void> setSkillLevel() async {
showModalBottomSheet(
context: context,
builder: (_) => StatefulBuilder(
builder: _skillLevelSliderTheme,
),
);
}
Future<void> setMoveTime() async {
showModalBottomSheet(
context: context,
builder: (_) => StatefulBuilder(
builder: _moveTimeSliderTheme,
),
);
}
Future<void> setWhoMovesFirst(bool value) async {
setState(() => Config.aiMovesFirst = !value);
debugPrint("[config] aiMovesFirst: ${Config.aiMovesFirst}");
Config.save();
}
Future<void> setAiIsLazy(bool value) async {
setState(() => Config.aiIsLazy = value);
debugPrint("[config] aiMovesFirst: $value");
Config.save();
}
void setAlgorithm() {
Future<void> callback(int? algorithm) async {
debugPrint("[config] algorithm = $algorithm");
Navigator.pop(context);
setState(() => Config.algorithm = algorithm ?? 2);
debugPrint("[config] Config.algorithm: ${Config.algorithm}");
Config.save();
}
showModalBottomSheet(
context: context,
builder: (BuildContext context) => Semantics(
label: S.of(context).algorithm,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: const Text('Alpha-Beta'),
groupValue: Config.algorithm,
value: 0,
onChanged: callback,
),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: const Text('PVS'),
groupValue: Config.algorithm,
value: 1,
onChanged: callback,
),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: const Text('MTD(f)'),
groupValue: Config.algorithm,
value: 2,
onChanged: callback,
),
],
),
),
);
}
Future<void> setDrawOnHumanExperience(bool value) async {
setState(() => Config.drawOnHumanExperience = value);
debugPrint("[config] drawOnHumanExperience: $value");
Config.save();
}
Future<void> setConsiderMobility(bool value) async {
setState(() => Config.considerMobility = value);
debugPrint("[config] considerMobility: $value");
Config.save();
}
Future<void> setIsAutoRestart(bool value) async {
setState(() => Config.isAutoRestart = value);
debugPrint("[config] isAutoRestart: $value");
Config.save();
}
Future<void> setIsAutoChangeFirstMove(bool value) async {
setState(() => Config.isAutoChangeFirstMove = value);
debugPrint("[config] isAutoChangeFirstMove: $value");
Config.save();
}
Future<void> setResignIfMostLose(bool value) async {
setState(() => Config.resignIfMostLose = value);
debugPrint("[config] resignIfMostLose: $value");
Config.save();
}
Future<void> setShufflingEnabled(bool value) async {
setState(() => Config.shufflingEnabled = value);
debugPrint("[config] shufflingEnabled: $value");
Config.save();
}
Future<void> setLearnEndgame(bool value) async {
setState(() => Config.learnEndgame = value);
debugPrint("[config] learnEndgame: $value");
Config.save();
}
Future<void> setOpeningBook(bool value) async {
setState(() => Config.openingBook = value);
debugPrint("[config] openingBook: $value");
Config.save();
}
Future<void> setTone(bool value) async {
setState(() => Config.toneEnabled = value);
debugPrint("[config] toneEnabled: $value");
Config.save();
}
Future<void> setKeepMuteWhenTakingBack(bool value) async {
setState(() => Config.keepMuteWhenTakingBack = value);
debugPrint("[config] keepMuteWhenTakingBack: $value");
Config.save();
}
Future<void> setScreenReaderSupport(bool value) async {
setState(() => Config.screenReaderSupport = value);
debugPrint("[config] screenReaderSupport: $value");
Config.save();
}
Future<void> setDeveloperMode(bool value) async {
setState(() => Config.developerMode = value);
debugPrint("[config] developerMode: $value");
Config.save();
}
Future<void> setExperimentsEnabled(bool value) async {
setState(() => Config.experimentsEnabled = value);
debugPrint("[config] experimentsEnabled: $value");
Config.save();
}
// Display
Future<void> setLanguage(String value) async {
setState(() => Config.languageCode = value);
debugPrint("[config] languageCode: $value");
Config.save();
}
Future<void> setIsPieceCountInHandShown(bool value) async {
setState(() => Config.isPieceCountInHandShown = value);
debugPrint("[config] isPieceCountInHandShown: $value");
Config.save();
}
Future<void> setIsNotationsShown(bool value) async {
setState(() => Config.isNotationsShown = value);
debugPrint("[config] isNotationsShown: $value");
Config.save();
}
Future<void> setIsHistoryNavigationToolbarShown(bool value) async {
setState(() => Config.isHistoryNavigationToolbarShown = value);
debugPrint("[config] isHistoryNavigationToolbarShown: $value");
Config.save();
}
Future<void> setStandardNotationEnabled(bool value) async {
setState(() => Config.standardNotationEnabled = value);
debugPrint("[config] standardNotationEnabled: $value");
Config.save();
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
class HelpScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Color(Config.darkBackgroundColor),
centerTitle: true,
title: Text(
S.of(context).howToPlay,
style: TextStyle(
color: AppTheme.helpTextColor,
),
),
),
backgroundColor: Color(Config.darkBackgroundColor),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Text(
S.of(context).helpContent,
style: TextStyle(
fontSize: Config.fontSize,
color: AppTheme.helpTextColor,
),
),
),
);
}
}

View File

@ -0,0 +1,261 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
part of 'package:sanmill/screens/navigation_home_screen.dart';
enum DrawerIndex {
humanVsAi,
humanVsHuman,
aiVsAi,
preferences,
ruleSettings,
personalization,
feedback,
Help,
About
}
class DrawerListItem {
const DrawerListItem({
required this.index,
required this.title,
required this.icon,
});
final DrawerIndex index;
final String title;
final Icon icon;
}
class HomeDrawer extends StatelessWidget {
const HomeDrawer({
Key? key,
required this.screenIndex,
required this.iconAnimationController,
required this.callBackIndex,
required this.items,
}) : super(key: key);
final AnimationController iconAnimationController;
final DrawerIndex screenIndex;
final Function(DrawerIndex) callBackIndex;
final List<DrawerListItem> items;
@override
Widget build(BuildContext context) {
return Material(
color: Color(Config.drawerBackgroundColor),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
_DrawerHeader(
iconAnimationController: iconAnimationController,
),
Divider(height: 1, color: AppTheme.drawerDividerColor),
ListView.builder(
padding: const EdgeInsets.only(top: 4.0),
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
itemCount: items.length,
itemBuilder: _buildChildren,
),
//drawFooter,
],
),
);
}
Future<void> navigationToScreen(DrawerIndex index) async {
callBackIndex(index);
}
Widget _buildChildren(BuildContext context, int index) {
final listItem = items[index];
final bool isSelected = screenIndex == listItem.index;
final bool ltr =
getBidirectionality(context) == Bidirectionality.leftToRight;
const double radius = 28.0;
final animatedBuilder = AnimatedBuilder(
animation: iconAnimationController,
builder: (BuildContext context, Widget? child) {
return Transform(
transform: Matrix4.translationValues(
(MediaQuery.of(context).size.width * 0.75 - 64) *
(1.0 - iconAnimationController.value - 1.0),
0.0,
0.0,
),
child: child,
);
},
child: Container(
width: MediaQuery.of(context).size.width * 0.75 - 64,
height: 46,
decoration: BoxDecoration(
color: Color(Config.drawerHighlightItemColor),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(ltr ? 0 : radius),
topRight: Radius.circular(ltr ? radius : 0),
bottomLeft: Radius.circular(ltr ? 0 : radius),
bottomRight: Radius.circular(ltr ? radius : 0),
),
),
),
);
final listItemIcon = Icon(
listItem.icon.icon,
color: isSelected
? Color(Config.drawerTextColor) // TODO: drawerHighlightTextColor
: Color(Config.drawerTextColor),
);
final child = Row(
children: <Widget>[
const SizedBox(height: 46.0, width: 6.0),
const Padding(
padding: EdgeInsets.all(4.0),
),
listItemIcon,
const Padding(
padding: EdgeInsets.all(4.0),
),
Text(
listItem.title,
style: TextStyle(
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
fontSize: Config.fontSize,
color: isSelected
? Color(
Config.drawerTextColor,
) // TODO: drawerHighlightTextColor
: Color(Config.drawerTextColor),
),
),
],
);
return InkWell(
splashColor: AppTheme.drawerSplashColor,
highlightColor: AppTheme.drawerHighlightColor,
onTap: () => navigationToScreen(listItem.index),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: isSelected
? Stack(
children: <Widget>[
child,
animatedBuilder,
],
)
: child,
),
);
}
}
class _DrawerHeader extends StatelessWidget {
const _DrawerHeader({
Key? key,
required this.iconAnimationController,
}) : super(key: key);
final AnimationController iconAnimationController;
@override
Widget build(BuildContext context) {
const String tag = "[home_drawer]";
final List<Color> animatedTextsColors = [
Color(Config.drawerTextColor),
Colors.black,
Colors.blue,
Colors.yellow,
Colors.red,
Color(Config.darkBackgroundColor),
Color(Config.boardBackgroundColor),
Color(Config.drawerHighlightItemColor),
];
final rotationTransition = RotationTransition(
turns: AlwaysStoppedAnimation<double>(
Tween<double>(begin: 0.0, end: 24.0)
.animate(
CurvedAnimation(
parent: iconAnimationController,
curve: Curves.fastOutSlowIn,
),
)
.value /
360,
),
);
final scaleTransition = ScaleTransition(
scale: AlwaysStoppedAnimation<double>(
1.0 - (iconAnimationController.value) * 0.2,
),
child: rotationTransition,
);
final animatedBuilder = AnimatedBuilder(
animation: iconAnimationController,
builder: (_, __) => scaleTransition,
);
final animation = GestureDetector(
child: AnimatedTextKit(
animatedTexts: [
ColorizeAnimatedText(
S.of(context).appName,
textStyle: TextStyle(
fontSize: Config.fontSize + 16,
fontWeight: FontWeight.w600,
),
colors: animatedTextsColors,
speed: const Duration(seconds: 3),
),
],
pause: const Duration(seconds: 3),
repeatForever: true,
stopPauseOnTap: true,
onTap: () => debugPrint("$tag DoubleTap to enable developer mode."),
),
onDoubleTap: () {
Developer.developerModeEnabled = true;
debugPrint("$tag Developer mode enabled.");
},
);
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// TODO: can animatedBuilder be removed? does not appear in the widget tree
animatedBuilder,
Padding(
padding: EdgeInsets.only(top: isLargeScreen ? 30 : 8, left: 4),
child: ExcludeSemantics(child: animation),
),
],
),
);
}
}

View File

@ -0,0 +1,54 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/shared/common/constants.dart';
class LicenseAgreementPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: rootBundle.loadString(Constants.gplLicenseFilename),
builder: (context, data) {
late final String _data;
if (!data.hasData) {
_data = 'Nothing to show';
} else {
_data = data.data!;
}
return Scaffold(
appBar: AppBar(
title: Text(S.of(context).license),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Text(
_data,
style: const TextStyle(fontFamily: 'Monospace', fontSize: 12),
textAlign: TextAlign.left,
),
),
);
},
);
}
}

View File

@ -19,26 +19,29 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:feedback/feedback.dart';
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/engine/engine.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/mill/game.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/style/colors.dart';
import 'package:sanmill/widgets/about_page.dart';
import 'package:sanmill/widgets/drawer_user_controller.dart';
import 'package:sanmill/widgets/help_screen.dart';
import 'package:sanmill/widgets/home_drawer.dart';
import 'package:sanmill/screens/about_page.dart';
import 'package:sanmill/screens/game_page/game_page.dart';
import 'package:sanmill/screens/game_settings_page.dart';
import 'package:sanmill/screens/help_screen.dart';
import 'package:sanmill/screens/personalization_settings_page.dart';
import 'package:sanmill/screens/rule_settings_page.dart';
import 'package:sanmill/services/engine/engine.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/common/constants.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
import 'game_page.dart';
import 'game_settings_page.dart';
import 'personalization_settings_page.dart';
import 'rule_settings_page.dart';
part 'package:sanmill/screens/home_drawer.dart';
part 'package:sanmill/shared/drawer_controller.dart';
class NavigationHomeScreen extends StatefulWidget {
@override
@ -46,48 +49,39 @@ class NavigationHomeScreen extends StatefulWidget {
}
class _NavigationHomeScreenState extends State<NavigationHomeScreen> {
Widget? screenView;
DrawerIndex? drawerIndex;
late Widget screenView;
late DrawerIndex drawerIndex;
@override
void initState() {
drawerIndex = DrawerIndex.humanVsAi;
screenView = GamePage(EngineType.humanVsAi);
screenView = const GamePage(EngineType.humanVsAi);
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
color: UIColors.nearlyWhite,
child: SafeArea(
top: false,
bottom: false,
child: Scaffold(
backgroundColor: AppTheme.navigationHomeScreenBackgroundColor,
body: DrawerUserController(
screenIndex: drawerIndex,
drawerWidth: MediaQuery.of(context).size.width * 0.75,
onDrawerCall: (DrawerIndex index) {
// callback from drawer for replace screen
// as user need with passing DrawerIndex (Enum index)
changeIndex(index);
},
// we replace screen view as
// we need on navigate starting screens
screenView: screenView,
),
),
return Material(
color: AppTheme.navigationHomeScreenBackgroundColor,
child: DrawerController(
screenIndex: drawerIndex,
drawerWidth: MediaQuery.of(context).size.width * 0.75,
onDrawerCall: changeIndex,
// we replace screen view as
// we need on navigate starting screens
screenView: screenView,
),
);
}
/// callback from drawer for replace screen
/// as user need with passing DrawerIndex (Enum index)
void changeIndex(DrawerIndex index) {
if (drawerIndex == index && drawerIndex != DrawerIndex.feedback) {
return;
}
var drawerMap = {
final drawerMap = {
DrawerIndex.humanVsAi: EngineType.humanVsAi,
DrawerIndex.humanVsHuman: EngineType.humanVsHuman,
DrawerIndex.aiVsAi: EngineType.aiVsAi,
@ -95,28 +89,21 @@ class _NavigationHomeScreenState extends State<NavigationHomeScreen> {
drawerIndex = index;
var engineType = drawerMap[drawerIndex!];
if (engineType != null) {
setState(() {
Game.instance.setWhoIsAi(engineType);
// TODO: use switch case
final engineType = drawerMap[drawerIndex];
setState(() {
if (engineType != null) {
gameInstance.setWhoIsAi(engineType);
screenView = GamePage(engineType);
});
} else if (drawerIndex == DrawerIndex.preferences) {
setState(() {
} else if (drawerIndex == DrawerIndex.preferences) {
screenView = GameSettingsPage();
});
} else if (drawerIndex == DrawerIndex.ruleSettings) {
setState(() {
} else if (drawerIndex == DrawerIndex.ruleSettings) {
screenView = RuleSettingsPage();
});
} else if (drawerIndex == DrawerIndex.personalization) {
setState(() {
} else if (drawerIndex == DrawerIndex.personalization) {
screenView = PersonalizationSettingsPage();
});
} else if (drawerIndex == DrawerIndex.feedback && !Config.developerMode) {
setState(() {
} else if (drawerIndex == DrawerIndex.feedback && !Config.developerMode) {
if (Platform.isWindows) {
print("flutter_email_sender does not support Windows.");
debugPrint("flutter_email_sender does not support Windows.");
//_launchFeedback();
} else {
BetterFeedback.of(context).show((feedback) async {
@ -124,33 +111,28 @@ class _NavigationHomeScreenState extends State<NavigationHomeScreen> {
final screenshotFilePath =
await writeImageToStorage(feedback.screenshot);
final packageInfo = await PackageInfo.fromPlatform();
var _version =
final _version =
'${packageInfo.version} (${packageInfo.buildNumber})';
final Email email = Email(
body: feedback.text,
subject: Constants.feedbackSubjectPrefix +
"$_version" +
_version +
Constants.feedbackSubjectSuffix,
recipients: [Constants.recipients],
attachmentPaths: [screenshotFilePath],
isHTML: false,
);
await FlutterEmailSender.send(email);
});
}
});
} else if (drawerIndex == DrawerIndex.Help && !Config.developerMode) {
setState(() {
} else if (drawerIndex == DrawerIndex.Help && !Config.developerMode) {
screenView = HelpScreen();
});
} else if (drawerIndex == DrawerIndex.About && !Config.developerMode) {
setState(() {
} else if (drawerIndex == DrawerIndex.About && !Config.developerMode) {
screenView = AboutPage();
});
} else {
//do in your way......
}
} else {
//do in your way......
}
});
}
Future<String> writeImageToStorage(Uint8List feedbackScreenshot) async {

View File

@ -0,0 +1,176 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/generated/oss_licenses.dart';
import 'package:url_launcher/url_launcher.dart';
// TODO: use flutters build in viewLicense function
class FlutterLicense extends LicenseEntry {
@override
final List<String> packages;
@override
final List<LicenseParagraph> paragraphs;
FlutterLicense(this.packages, this.paragraphs);
}
/// display all used packages and their license
class OssLicensesPage extends StatelessWidget {
static Future<List<String>> loadLicenses() async {
Stream<LicenseEntry> licenses() async* {
yield FlutterLicense(
['Sound Effects'],
[
const LicenseParagraph(
'CC-0\nhttps://freesound.org/people/unfa/sounds/243749/',
0,
),
],
);
}
LicenseRegistry.addLicense(licenses);
// merging non-dart based dependency list using LicenseRegistry.
final ossKeys = ossLicenses.keys.toList();
final lm = <String, List<String>>{};
await for (final l in LicenseRegistry.licenses) {
for (final p in l.packages) {
if (!ossKeys.contains(p)) {
final lp = lm.putIfAbsent(p, () => []);
lp.addAll(l.paragraphs.map((p) => p.text));
ossKeys.add(p);
}
}
}
for (final key in lm.keys) {
ossLicenses[key] = {'license': lm[key]!.join('\n')};
}
return ossKeys..sort();
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(S.of(context).ossLicenses),
),
body: FutureBuilder<List<String>>(
future: loadLicenses(),
builder: (context, snapshot) => ListView.separated(
itemCount: snapshot.data?.length ?? 0,
itemBuilder: (context, index) {
final key = snapshot.data![index];
final ossl = ossLicenses[key] as Map<String, dynamic>;
final version = ossl['version'];
final desc = ossl['description'] as String?;
return ListTile(
title: Text('$key $version'),
subtitle: desc != null ? Text(desc) : null,
trailing: const Icon(FluentIcons.chevron_right_24_regular),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => MiscOssLicenseSingle(name: key, json: ossl),
),
),
);
},
separatorBuilder: (context, index) => const Divider(),
),
),
);
}
class MiscOssLicenseSingle extends StatelessWidget {
final String name;
final Map<String, dynamic> json;
const MiscOssLicenseSingle({
required this.name,
required this.json,
});
String get version => json['version'] as String? ?? "";
String? get description => json['description'] as String?;
String get licenseText => json['license'] as String;
String? get homepage => json['homepage'] as String?;
String get _bodyText => licenseText.split('\n').map((line) {
if (line.startsWith('//')) line = line.substring(2);
return line.trim();
}).join('\n');
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text('$name $version')),
backgroundColor: Theme.of(context).canvasColor,
body: ListView(
children: <Widget>[
if (description != null)
Padding(
padding: const EdgeInsets.only(
top: 12.0,
left: 12.0,
right: 12.0,
),
child: Text(
description!,
style: Theme.of(context)
.textTheme
.bodyText2!
.copyWith(fontWeight: FontWeight.bold),
),
),
if (homepage != null)
Padding(
padding: const EdgeInsets.only(
top: 12.0,
left: 12.0,
right: 12.0,
),
child: InkWell(
child: Text(
homepage!,
style: const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
),
onTap: () => launch(homepage!),
),
),
if (description != null || homepage != null) const Divider(),
Padding(
padding: const EdgeInsets.only(
top: 12.0,
left: 12.0,
right: 12.0,
),
child: Text(
_bodyText,
style: Theme.of(context).textTheme.bodyText2,
),
),
],
),
);
}

View File

@ -18,16 +18,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/settings_card.dart';
import 'package:sanmill/widgets/settings_list_tile.dart';
import 'package:sanmill/widgets/settings_switch_list_tile.dart';
import 'list_item_divider.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/common/constants.dart';
import 'package:sanmill/shared/settings/settings_card.dart';
import 'package:sanmill/shared/settings/settings_list_tile.dart';
import 'package:sanmill/shared/settings/settings_switch_list_tile.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
class PersonalizationSettingsPage extends StatefulWidget {
@override
@ -38,21 +36,16 @@ class PersonalizationSettingsPage extends StatefulWidget {
class _PersonalizationSettingsPageState
extends State<PersonalizationSettingsPage> {
// create some values
Color pickerColor = Color(0xFF808080);
Color currentColor = Color(0xFF808080);
@override
void initState() {
super.initState();
}
Color pickerColor = const Color(0xFF808080);
Color currentColor = const Color(0xFF808080);
// ValueChanged<Color> callback
void changeColor(Color color) {
setState(() => pickerColor = color);
}
showColorDialog(String colorString) async {
Map<String, int> colorStrToVal = {
Future<void> showColorDialog(String colorString) async {
final Map<String, int> colorStrToVal = {
S.of(context).boardColor: Config.boardBackgroundColor,
S.of(context).backgroundColor: Config.darkBackgroundColor,
S.of(context).lineColor: Config.boardLineColor,
@ -73,9 +66,9 @@ class _PersonalizationSettingsPageState
Config.navigationToolbarIconColor,
};
AlertDialog alert = AlertDialog(
final AlertDialog alert = AlertDialog(
title: Text(
S.of(context).pick + " " + colorString,
"${S.of(context).pick} $colorString",
style: TextStyle(
fontSize: Config.fontSize + 4,
),
@ -84,7 +77,6 @@ class _PersonalizationSettingsPageState
child: ColorPicker(
pickerColor: Color(colorStrToVal[colorString]!),
onColorChanged: changeColor,
showLabel: true,
),
),
actions: <Widget>[
@ -98,7 +90,7 @@ class _PersonalizationSettingsPageState
onPressed: () {
setState(() => currentColor = pickerColor);
print("[config] pickerColor.value: ${pickerColor.value}");
debugPrint("[config] pickerColor.value: ${pickerColor.value}");
if (colorString == S.of(context).boardColor) {
Config.boardBackgroundColor = pickerColor.value;
@ -136,7 +128,7 @@ class _PersonalizationSettingsPageState
}
Config.save();
Navigator.of(context).pop();
Navigator.pop(context);
},
),
TextButton(
@ -147,7 +139,7 @@ class _PersonalizationSettingsPageState
),
),
onPressed: () {
Navigator.of(context).pop();
Navigator.pop(context);
},
),
],
@ -162,20 +154,22 @@ class _PersonalizationSettingsPageState
);
}
SliderTheme _boardBorderLineWidthSliderTheme(context, setState) {
SliderTheme _boardBorderLineWidthSliderTheme(
BuildContext context,
Function setState,
) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).boardBorderLineWidth,
child: Slider(
value: Config.boardBorderLineWidth.toDouble(),
min: 0.0,
value: Config.boardBorderLineWidth,
max: 20.0,
divisions: 200,
label: Config.boardBorderLineWidth.toStringAsFixed(1),
onChanged: (value) {
setState(() {
print("[config] BoardBorderLineWidth value: $value");
debugPrint("[config] BoardBorderLineWidth value: $value");
Config.boardBorderLineWidth = value;
Config.save();
});
@ -185,31 +179,31 @@ class _PersonalizationSettingsPageState
);
}
setBoardBorderLineWidth() async {
Future<void> setBoardBorderLineWidth() async {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return _boardBorderLineWidthSliderTheme(context, setState);
},
builder: (_) => StatefulBuilder(
builder: _boardBorderLineWidthSliderTheme,
),
);
}
SliderTheme _boardInnerLineWidthSliderTheme(context, setState) {
SliderTheme _boardInnerLineWidthSliderTheme(
BuildContext context,
Function setState,
) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).boardInnerLineWidth,
child: Slider(
value: Config.boardInnerLineWidth.toDouble(),
min: 0.0,
value: Config.boardInnerLineWidth,
max: 20.0,
divisions: 200,
label: Config.boardInnerLineWidth.toStringAsFixed(1),
onChanged: (value) {
setState(() {
print("[config] BoardInnerLineWidth value: $value");
debugPrint("[config] BoardInnerLineWidth value: $value");
Config.boardInnerLineWidth = value;
Config.save();
});
@ -219,26 +213,24 @@ class _PersonalizationSettingsPageState
);
}
setBoardInnerLineWidth() async {
Future<void> setBoardInnerLineWidth() async {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return _boardInnerLineWidthSliderTheme(context, setState);
},
builder: (_) => StatefulBuilder(
builder: _boardInnerLineWidthSliderTheme,
),
);
}
setPointStyle() {
callback(int? pointStyle) async {
Navigator.of(context).pop();
void setPointStyle() {
Future<void> callback(int? pointStyle) async {
Navigator.pop(context);
setState(() {
Config.pointStyle = pointStyle ?? 0;
});
setState(
() => Config.pointStyle = pointStyle ?? 0,
);
print("[config] pointStyle: $pointStyle");
debugPrint("[config] pointStyle: $pointStyle");
Config.save();
}
@ -257,7 +249,6 @@ class _PersonalizationSettingsPageState
value: 0,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text(S.of(context).solid),
@ -265,16 +256,14 @@ class _PersonalizationSettingsPageState
value: 1,
onChanged: callback,
),
ListItemDivider(),
/*
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text(S.of(context).hollow),
title: const Text(S.of(context).hollow),
groupValue: Config.pointStyle,
value: 2,
onChanged: callback,
),
ListItemDivider(),
*/
],
),
@ -282,20 +271,19 @@ class _PersonalizationSettingsPageState
);
}
SliderTheme _pointWidthSliderTheme(context, setState) {
SliderTheme _pointWidthSliderTheme(BuildContext context, Function setState) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).pointWidth,
child: Slider(
value: Config.pointWidth.toDouble(),
min: 0,
value: Config.pointWidth,
max: 30.0,
divisions: 30,
label: Config.pointWidth.toStringAsFixed(1),
onChanged: (value) {
setState(() {
print("[config] pointWidth value: $value");
debugPrint("[config] pointWidth value: $value");
Config.pointWidth = value;
Config.save();
});
@ -305,31 +293,28 @@ class _PersonalizationSettingsPageState
);
}
setPointWidth() async {
Future<void> setPointWidth() async {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return _pointWidthSliderTheme(context, setState);
},
builder: (_) => StatefulBuilder(
builder: _pointWidthSliderTheme,
),
);
}
SliderTheme _pieceWidthSliderTheme(context, setState) {
SliderTheme _pieceWidthSliderTheme(BuildContext context, Function setState) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).pieceWidth,
child: Slider(
value: Config.pieceWidth.toDouble(),
value: Config.pieceWidth,
min: 0.5,
max: 1.0,
divisions: 50,
label: Config.pieceWidth.toStringAsFixed(1),
onChanged: (value) {
setState(() {
print("[config] pieceWidth value: $value");
debugPrint("[config] pieceWidth value: $value");
Config.pieceWidth = value;
Config.save();
});
@ -339,31 +324,29 @@ class _PersonalizationSettingsPageState
);
}
setPieceWidth() async {
Future<void> setPieceWidth() async {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return _pieceWidthSliderTheme(context, setState);
},
builder: (_) => StatefulBuilder(
builder: _pieceWidthSliderTheme,
),
);
}
SliderTheme _fontSizeSliderTheme(context, setState) {
SliderTheme _fontSizeSliderTheme(BuildContext context, Function setState) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).fontSize,
child: Slider(
value: Config.fontSize.toDouble(),
value: Config.fontSize,
min: 16,
max: 32,
divisions: 16,
label: Config.fontSize.toStringAsFixed(1),
onChanged: (value) {
setState(() {
print("[config] fontSize value: $value");
debugPrint("[config] fontSize value: $value");
Config.fontSize = value;
Config.save();
});
@ -373,31 +356,28 @@ class _PersonalizationSettingsPageState
);
}
setFontSize() async {
Future<void> setFontSize() async {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return _fontSizeSliderTheme(context, setState);
},
builder: (_) => StatefulBuilder(
builder: _fontSizeSliderTheme,
),
);
}
SliderTheme _boardTopSliderTheme(context, setState) {
SliderTheme _boardTopSliderTheme(BuildContext context, Function setState) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).boardTop,
child: Slider(
value: Config.boardTop.toDouble(),
min: 0.0,
value: Config.boardTop,
max: 288.0,
divisions: 288,
label: Config.boardTop.toStringAsFixed(1),
onChanged: (value) {
setState(() {
print("[config] BoardTop value: $value");
debugPrint("[config] BoardTop value: $value");
Config.boardTop = value;
Config.save();
});
@ -407,31 +387,31 @@ class _PersonalizationSettingsPageState
);
}
setBoardTop() async {
Future<void> setBoardTop() async {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return _boardTopSliderTheme(context, setState);
},
builder: (_) => StatefulBuilder(
builder: _boardTopSliderTheme,
),
);
}
SliderTheme _animationDurationSliderTheme(context, setState) {
SliderTheme _animationDurationSliderTheme(
BuildContext context,
Function setState,
) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).animationDuration,
child: Slider(
value: Config.animationDuration.toDouble(),
min: 0.0,
value: Config.animationDuration,
max: 5.0,
divisions: 50,
label: Config.animationDuration.toStringAsFixed(1),
onChanged: (value) {
setState(() {
print("[config] AnimationDuration value: $value");
debugPrint("[config] AnimationDuration value: $value");
Config.animationDuration = value;
Config.save();
});
@ -441,13 +421,11 @@ class _PersonalizationSettingsPageState
);
}
setAnimationDuration() async {
Future<void> setAnimationDuration() async {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return _animationDurationSliderTheme(context, setState);
},
builder: (_) => StatefulBuilder(
builder: _animationDurationSliderTheme,
),
);
}
@ -471,17 +449,17 @@ class _PersonalizationSettingsPageState
}
List<Widget> children(BuildContext context) {
langCallback(var langCode) async {
print("[config] languageCode = $langCode");
Future<void> langCallback([String? langCode]) async {
debugPrint("[config] languageCode = $langCode");
Navigator.of(context).pop();
Navigator.pop(context);
setState(() {
Config.languageCode = langCode ?? Constants.defaultLanguageCodeName;
S.load(Locale(Resources.of().languageCode));
});
print("[config] Config.languageCode: ${Config.languageCode}");
debugPrint("[config] Config.languageCode: ${Config.languageCode}");
Config.save();
}
@ -489,203 +467,147 @@ class _PersonalizationSettingsPageState
return <Widget>[
Text(S.of(context).display, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsListTile(
context: context,
titleString: S.of(context).language,
trailingString:
Config.languageCode == Constants.defaultLanguageCodeName
? ""
: languageCodeToStrings[Config.languageCode.toString()]!
.languageName,
Config.languageCode != Constants.defaultLanguageCodeName
? languageCodeToStrings[Config.languageCode]!.languageName
: "",
onTap: () => setLanguage(context, langCallback),
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.isPieceCountInHandShown,
onChanged: setIsPieceCountInHandShown,
titleString: S.of(context).isPieceCountInHandShown,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.isNotationsShown,
onChanged: setIsNotationsShown,
titleString: S.of(context).isNotationsShown,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.isHistoryNavigationToolbarShown,
onChanged: setIsHistoryNavigationToolbarShown,
titleString: S.of(context).isHistoryNavigationToolbarShown,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).boardBorderLineWidth,
onTap: setBoardBorderLineWidth),
ListItemDivider(),
titleString: S.of(context).boardBorderLineWidth,
onTap: setBoardBorderLineWidth,
),
SettingsListTile(
context: context,
titleString: S.of(context).boardInnerLineWidth,
onTap: setBoardInnerLineWidth,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).pointStyle,
onTap: setPointStyle,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).pointWidth,
onTap: setPointWidth,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).pieceWidth,
onTap: setPieceWidth,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).fontSize,
onTap: setFontSize,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).boardTop,
onTap: setBoardTop,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).animationDuration,
onTap: setAnimationDuration,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.standardNotationEnabled,
onChanged: setStandardNotationEnabled,
titleString: S.of(context).standardNotation,
),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).color, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsListTile(
context: context,
titleString: S.of(context).boardColor,
trailingColor: Config.boardBackgroundColor,
onTap: () => showColorDialog(S.of(context).boardColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).backgroundColor,
trailingColor: Config.darkBackgroundColor,
onTap: () => showColorDialog(S.of(context).backgroundColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).lineColor,
trailingColor: Config.boardLineColor,
onTap: () => showColorDialog(S.of(context).lineColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).whitePieceColor,
trailingColor: Config.whitePieceColor,
onTap: () => showColorDialog(S.of(context).whitePieceColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).blackPieceColor,
trailingColor: Config.blackPieceColor,
onTap: () => showColorDialog(S.of(context).blackPieceColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).pieceHighlightColor,
trailingColor: Config.pieceHighlightColor,
onTap: () => showColorDialog(S.of(context).pieceHighlightColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).messageColor,
trailingColor: Config.messageColor,
onTap: () => showColorDialog(S.of(context).messageColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).drawerColor,
trailingColor: Config.drawerColor,
onTap: () => showColorDialog(S.of(context).drawerColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).drawerBackgroundColor,
trailingColor: Config.drawerBackgroundColor,
onTap: () => showColorDialog(S.of(context).drawerBackgroundColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).drawerTextColor,
trailingColor: Config.drawerTextColor,
onTap: () => showColorDialog(S.of(context).drawerTextColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).drawerHighlightItemColor,
trailingColor: Config.drawerHighlightItemColor,
onTap: () =>
showColorDialog(S.of(context).drawerHighlightItemColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).mainToolbarBackgroundColor,
trailingColor: Config.mainToolbarBackgroundColor,
onTap: () =>
showColorDialog(S.of(context).mainToolbarBackgroundColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).mainToolbarIconColor,
trailingColor: Config.mainToolbarIconColor,
onTap: () => showColorDialog(S.of(context).mainToolbarIconColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).navigationToolbarBackgroundColor,
trailingColor: Config.navigationToolbarBackgroundColor,
onTap: () =>
showColorDialog(S.of(context).navigationToolbarBackgroundColor),
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).navigationToolbarIconColor,
trailingColor: Config.navigationToolbarIconColor,
onTap: () =>
@ -698,36 +620,28 @@ class _PersonalizationSettingsPageState
// Display
setIsPieceCountInHandShown(bool value) async {
setState(() {
Config.isPieceCountInHandShown = value;
});
Future<void> setIsPieceCountInHandShown(bool value) async {
setState(() => Config.isPieceCountInHandShown = value);
Config.save();
}
setIsNotationsShown(bool value) async {
setState(() {
Config.isNotationsShown = value;
});
Future<void> setIsNotationsShown(bool value) async {
setState(() => Config.isNotationsShown = value);
Config.save();
}
setIsHistoryNavigationToolbarShown(bool value) async {
setState(() {
Config.isHistoryNavigationToolbarShown = value;
});
Future<void> setIsHistoryNavigationToolbarShown(bool value) async {
setState(() => Config.isHistoryNavigationToolbarShown = value);
Config.save();
}
setStandardNotationEnabled(bool value) async {
setState(() {
Config.standardNotationEnabled = value;
});
Future<void> setStandardNotationEnabled(bool value) async {
setState(() => Config.standardNotationEnabled = value);
print("[config] standardNotationEnabled: $value");
debugPrint("[config] standardNotationEnabled: $value");
Config.save();
}

View File

@ -17,17 +17,15 @@
*/
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/mill/rule.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/settings_card.dart';
import 'package:sanmill/widgets/settings_list_tile.dart';
import 'package:sanmill/widgets/settings_switch_list_tile.dart';
import 'list_item_divider.dart';
import 'snack_bar.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/settings/settings_card.dart';
import 'package:sanmill/shared/settings/settings_list_tile.dart';
import 'package:sanmill/shared/settings/settings_switch_list_tile.dart';
import 'package:sanmill/shared/snack_bar.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
class RuleSettingsPage extends StatefulWidget {
@override
@ -35,11 +33,6 @@ class RuleSettingsPage extends StatefulWidget {
}
class _RuleSettingsPageState extends State<RuleSettingsPage> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -60,74 +53,57 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
return <Widget>[
Text(S.of(context).general, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsListTile(
context: context,
titleString: S.of(context).piecesCount,
subtitleString: S.of(context).piecesCount_Detail,
trailingString: Config.piecesCount.toString(),
onTap: setNTotalPiecesEachSide,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.hasDiagonalLines,
onChanged: setHasDiagonalLines,
titleString: S.of(context).hasDiagonalLines,
subtitleString: S.of(context).hasDiagonalLines_Detail,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).nMoveRule,
subtitleString: S.of(context).nMoveRule_Detail,
trailingString: Config.nMoveRule.toString(),
onTap: setNMoveRule,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).endgameNMoveRule,
subtitleString: S.of(context).endgameNMoveRule_Detail,
trailingString: Config.endgameNMoveRule.toString(),
onTap: setEndgameNMoveRule,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.threefoldRepetitionRule,
onChanged: setThreefoldRepetitionRule,
titleString: S.of(context).threefoldRepetitionRule,
subtitleString: S.of(context).threefoldRepetitionRule_Detail,
),
ListItemDivider(),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).placing, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsSwitchListTile(
context: context,
value: Config.hasBannedLocations,
onChanged: setHasBannedLocations,
titleString: S.of(context).hasBannedLocations,
subtitleString: S.of(context).hasBannedLocations_Detail,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.isWhiteLoseButNotDrawWhenBoardFull,
onChanged: setIsWhiteLoseButNotDrawWhenBoardFull,
titleString: S.of(context).isWhiteLoseButNotDrawWhenBoardFull,
subtitleString:
S.of(context).isWhiteLoseButNotDrawWhenBoardFull_Detail,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.mayOnlyRemoveUnplacedPieceInPlacingPhase,
onChanged: setMayOnlyRemoveUnplacedPieceInPlacingPhase,
titleString: S.of(context).removeUnplacedPiece,
@ -135,31 +111,25 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).moving, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
Config.experimentsEnabled
? SettingsSwitchListTile(
context: context,
value: Config.mayMoveInPlacingPhase,
onChanged: setMayMoveInPlacingPhase,
titleString: S.of(context).mayMoveInPlacingPhase,
subtitleString: S.of(context).mayMoveInPlacingPhase_Detail,
)
: SizedBox(height: 1),
Config.experimentsEnabled ? ListItemDivider() : SizedBox(height: 1),
if (Config.experimentsEnabled)
SettingsSwitchListTile(
value: Config.mayMoveInPlacingPhase,
onChanged: setMayMoveInPlacingPhase,
titleString: S.of(context).mayMoveInPlacingPhase,
subtitleString: S.of(context).mayMoveInPlacingPhase_Detail,
)
else
SettingsSwitchListTile(
value: Config.isDefenderMoveFirst,
onChanged: setIsDefenderMoveFirst,
titleString: S.of(context).isDefenderMoveFirst,
subtitleString: S.of(context).isDefenderMoveFirst_Detail,
),
SettingsSwitchListTile(
context: context,
value: Config.isDefenderMoveFirst,
onChanged: setIsDefenderMoveFirst,
titleString: S.of(context).isDefenderMoveFirst,
subtitleString: S.of(context).isDefenderMoveFirst_Detail,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.isLoseButNotChangeSideWhenNoWay,
onChanged: setIsLoseButNotChangeSideWhenNoWay,
titleString: S.of(context).isLoseButNotChangeSideWhenNoWay,
@ -168,21 +138,17 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).mayFly, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsSwitchListTile(
context: context,
value: Config.mayFly,
onChanged: setAllowFlyingAllowed,
titleString: S.of(context).mayFly,
subtitleString: S.of(context).mayFly_Detail,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).flyPieceCount,
subtitleString: S.of(context).flyPieceCount_Detail,
trailingString: Config.flyPieceCount.toString(),
@ -190,27 +156,22 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
const SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).removing, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsSwitchListTile(
context: context,
value: Config.mayRemoveFromMillsAlways,
onChanged: setAllowRemovePieceInMill,
titleString: S.of(context).mayRemoveFromMillsAlways,
subtitleString: S.of(context).mayRemoveFromMillsAlways_Detail,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.mayRemoveMultiple,
onChanged: setAllowRemoveMultiPiecesWhenCloseMultiMill,
titleString: S.of(context).mayRemoveMultiple,
subtitleString: S.of(context).mayRemoveMultiple_Detail,
),
ListItemDivider(),
],
),
];
@ -218,18 +179,18 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
// General
setNTotalPiecesEachSide() {
callback(int? piecesCount) async {
print("[config] piecesCount = $piecesCount");
void setNTotalPiecesEachSide() {
Future<void> callback(int? piecesCount) async {
debugPrint("[config] piecesCount = $piecesCount");
Navigator.of(context).pop();
Navigator.pop(context);
setState(() {
rule.piecesCount = Config.piecesCount =
piecesCount ?? (specialCountryAndRegion == "Iran" ? 12 : 9);
});
setState(
() => rule.piecesCount = Config.piecesCount =
piecesCount ?? (specialCountryAndRegion == "Iran" ? 12 : 9),
);
print("[config] rule.piecesCount: ${rule.piecesCount}");
debugPrint("[config] rule.piecesCount: ${rule.piecesCount}");
Config.save();
}
@ -243,53 +204,47 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
children: <Widget>[
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('9'),
title: const Text('9'),
groupValue: Config.piecesCount,
value: 9,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('10'),
title: const Text('10'),
groupValue: Config.piecesCount,
value: 10,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('11'),
title: const Text('11'),
groupValue: Config.piecesCount,
value: 11,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('12'),
title: const Text('12'),
groupValue: Config.piecesCount,
value: 12,
onChanged: callback,
),
ListItemDivider(),
],
),
),
);
}
setNMoveRule() {
callback(int? nMoveRule) async {
print("[config] nMoveRule = $nMoveRule");
void setNMoveRule() {
Future<void> callback(int? nMoveRule) async {
debugPrint("[config] nMoveRule = $nMoveRule");
Navigator.of(context).pop();
Navigator.pop(context);
setState(() {
rule.nMoveRule = Config.nMoveRule = nMoveRule ?? 100;
});
setState(() => rule.nMoveRule = Config.nMoveRule = nMoveRule ?? 100);
print("[config] rule.nMoveRule: ${rule.nMoveRule}");
debugPrint("[config] rule.nMoveRule: ${rule.nMoveRule}");
Config.save();
}
@ -303,62 +258,57 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
children: <Widget>[
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('30'),
title: const Text('30'),
groupValue: Config.nMoveRule,
value: 30,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('50'),
title: const Text('50'),
groupValue: Config.nMoveRule,
value: 50,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('60'),
title: const Text('60'),
groupValue: Config.nMoveRule,
value: 60,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('100'),
title: const Text('100'),
groupValue: Config.nMoveRule,
value: 100,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('200'),
title: const Text('200'),
groupValue: Config.nMoveRule,
value: 200,
onChanged: callback,
),
ListItemDivider(),
],
),
),
);
}
setEndgameNMoveRule() {
callback(int? endgameNMoveRule) async {
print("[config] endgameNMoveRule = $endgameNMoveRule");
void setEndgameNMoveRule() {
Future<void> callback(int? endgameNMoveRule) async {
debugPrint("[config] endgameNMoveRule = $endgameNMoveRule");
Navigator.of(context).pop();
Navigator.pop(context);
setState(() {
rule.endgameNMoveRule =
Config.endgameNMoveRule = endgameNMoveRule ?? 100;
});
setState(
() => rule.endgameNMoveRule =
Config.endgameNMoveRule = endgameNMoveRule ?? 100,
);
print("[config] rule.endgameNMoveRule: ${rule.endgameNMoveRule}");
debugPrint("[config] rule.endgameNMoveRule: ${rule.endgameNMoveRule}");
Config.save();
}
@ -372,85 +322,77 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
children: <Widget>[
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('5'),
title: const Text('5'),
groupValue: Config.endgameNMoveRule,
value: 5,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('10'),
title: const Text('10'),
groupValue: Config.endgameNMoveRule,
value: 10,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('20'),
title: const Text('20'),
groupValue: Config.endgameNMoveRule,
value: 20,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('30'),
title: const Text('30'),
groupValue: Config.endgameNMoveRule,
value: 30,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('50'),
title: const Text('50'),
groupValue: Config.endgameNMoveRule,
value: 50,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('60'),
title: const Text('60'),
groupValue: Config.endgameNMoveRule,
value: 60,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('100'),
title: const Text('100'),
groupValue: Config.endgameNMoveRule,
value: 100,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('200'),
title: const Text('200'),
groupValue: Config.endgameNMoveRule,
value: 200,
onChanged: callback,
),
ListItemDivider(),
],
),
),
);
}
setFlyPieceCount() {
callback(int? flyPieceCount) async {
print("[config] flyPieceCount = $flyPieceCount");
void setFlyPieceCount() {
Future<void> callback(int? flyPieceCount) async {
debugPrint("[config] flyPieceCount = $flyPieceCount");
Navigator.of(context).pop();
Navigator.pop(context);
setState(() {
rule.flyPieceCount = Config.flyPieceCount = flyPieceCount ?? 3;
});
setState(
() => rule.flyPieceCount = Config.flyPieceCount = flyPieceCount ?? 3,
);
print("[config] rule.flyPieceCount: ${rule.flyPieceCount}");
debugPrint("[config] rule.flyPieceCount: ${rule.flyPieceCount}");
Config.save();
}
@ -464,98 +406,93 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
children: <Widget>[
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('3'),
title: const Text('3'),
groupValue: Config.flyPieceCount,
value: 3,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('4'),
title: const Text('4'),
groupValue: Config.flyPieceCount,
value: 4,
onChanged: callback,
),
ListItemDivider(),
],
),
),
);
}
setHasDiagonalLines(bool value) async {
setState(() {
rule.hasDiagonalLines = Config.hasDiagonalLines = value;
});
Future<void> setHasDiagonalLines(bool value) async {
setState(() => rule.hasDiagonalLines = Config.hasDiagonalLines = value);
print("[config] rule.hasDiagonalLines: $value");
debugPrint("[config] rule.hasDiagonalLines: $value");
Config.save();
}
setAllowFlyingAllowed(bool value) async {
setState(() {
rule.mayFly = Config.mayFly = value;
});
Future<void> setAllowFlyingAllowed(bool value) async {
setState(() => rule.mayFly = Config.mayFly = value);
print("[config] rule.mayFly: $value");
debugPrint("[config] rule.mayFly: $value");
Config.save();
}
setThreefoldRepetitionRule(bool value) async {
setState(() {
rule.threefoldRepetitionRule = Config.threefoldRepetitionRule = value;
});
Future<void> setThreefoldRepetitionRule(bool value) async {
setState(
() =>
rule.threefoldRepetitionRule = Config.threefoldRepetitionRule = value,
);
print("[config] rule.threefoldRepetitionRule: $value");
debugPrint("[config] rule.threefoldRepetitionRule: $value");
Config.save();
}
// Placing
setHasBannedLocations(bool value) async {
setState(() {
rule.hasBannedLocations = Config.hasBannedLocations = value;
});
Future<void> setHasBannedLocations(bool value) async {
setState(() => rule.hasBannedLocations = Config.hasBannedLocations = value);
print("[config] rule.hasBannedLocations: $value");
debugPrint("[config] rule.hasBannedLocations: $value");
Config.save();
}
setIsWhiteLoseButNotDrawWhenBoardFull(bool value) async {
setState(() {
rule.isWhiteLoseButNotDrawWhenBoardFull =
Config.isWhiteLoseButNotDrawWhenBoardFull = value;
});
Future<void> setIsWhiteLoseButNotDrawWhenBoardFull(bool value) async {
setState(
() => rule.isWhiteLoseButNotDrawWhenBoardFull =
Config.isWhiteLoseButNotDrawWhenBoardFull = value,
);
print("[config] rule.isWhiteLoseButNotDrawWhenBoardFull: $value");
debugPrint("[config] rule.isWhiteLoseButNotDrawWhenBoardFull: $value");
Config.save();
}
setMayOnlyRemoveUnplacedPieceInPlacingPhase(bool value) async {
setState(() {
rule.mayOnlyRemoveUnplacedPieceInPlacingPhase =
Config.mayOnlyRemoveUnplacedPieceInPlacingPhase = value;
});
Future<void> setMayOnlyRemoveUnplacedPieceInPlacingPhase(bool value) async {
setState(
() => rule.mayOnlyRemoveUnplacedPieceInPlacingPhase =
Config.mayOnlyRemoveUnplacedPieceInPlacingPhase = value,
);
print("[config] rule.mayOnlyRemoveUnplacedPieceInPlacingPhase: $value");
debugPrint(
"[config] rule.mayOnlyRemoveUnplacedPieceInPlacingPhase: $value",
);
Config.save();
}
// Moving
setMayMoveInPlacingPhase(bool value) async {
setState(() {
rule.mayMoveInPlacingPhase = Config.mayMoveInPlacingPhase = value;
});
Future<void> setMayMoveInPlacingPhase(bool value) async {
setState(
() => rule.mayMoveInPlacingPhase = Config.mayMoveInPlacingPhase = value,
);
print("[config] rule.mayMoveInPlacingPhase: $value");
debugPrint("[config] rule.mayMoveInPlacingPhase: $value");
Config.save();
@ -565,57 +502,56 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
}
}
setIsDefenderMoveFirst(bool value) async {
setState(() {
rule.isDefenderMoveFirst = Config.isDefenderMoveFirst = value;
});
Future<void> setIsDefenderMoveFirst(bool value) async {
setState(
() => rule.isDefenderMoveFirst = Config.isDefenderMoveFirst = value,
);
print("[config] rule.isDefenderMoveFirst: $value");
debugPrint("[config] rule.isDefenderMoveFirst: $value");
Config.save();
}
setIsLoseButNotChangeSideWhenNoWay(bool value) async {
setState(() {
rule.isLoseButNotChangeSideWhenNoWay =
Config.isLoseButNotChangeSideWhenNoWay = value;
});
Future<void> setIsLoseButNotChangeSideWhenNoWay(bool value) async {
setState(
() => rule.isLoseButNotChangeSideWhenNoWay =
Config.isLoseButNotChangeSideWhenNoWay = value,
);
print("[config] rule.isLoseButNotChangeSideWhenNoWay: $value");
debugPrint("[config] rule.isLoseButNotChangeSideWhenNoWay: $value");
Config.save();
}
// Removing
setAllowRemovePieceInMill(bool value) async {
setState(() {
rule.mayRemoveFromMillsAlways = Config.mayRemoveFromMillsAlways = value;
});
Future<void> setAllowRemovePieceInMill(bool value) async {
setState(
() => rule.mayRemoveFromMillsAlways =
Config.mayRemoveFromMillsAlways = value,
);
print("[config] rule.mayRemoveFromMillsAlways: $value");
debugPrint("[config] rule.mayRemoveFromMillsAlways: $value");
Config.save();
}
setAllowRemoveMultiPiecesWhenCloseMultiMill(bool value) async {
setState(() {
rule.mayRemoveMultiple = Config.mayRemoveMultiple = value;
});
Future<void> setAllowRemoveMultiPiecesWhenCloseMultiMill(bool value) async {
setState(
() => rule.mayRemoveMultiple = Config.mayRemoveMultiple = value,
);
print("[config] rule.mayRemoveMultiple: $value");
debugPrint("[config] rule.mayRemoveMultiple: $value");
Config.save();
}
// Unused
setNPiecesAtLeast(int value) async {
setState(() {
rule.piecesAtLeastCount = Config.piecesAtLeastCount = value;
});
Future<void> setNPiecesAtLeast(int value) async {
setState(() => rule.piecesAtLeastCount = Config.piecesAtLeastCount = value);
print("[config] rule.piecesAtLeastCount: $value");
debugPrint("[config] rule.piecesAtLeastCount: $value");
Config.save();
}

View File

@ -18,29 +18,33 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:soundpool/soundpool.dart';
import 'package:stack_trace/stack_trace.dart';
class Audios {
const Audios._();
//static AudioPlayer? _player;
static Soundpool? _soundpool;
// TODO: use enum for the sounds
static int? _alarmSoundStreamId;
static var drawSoundId;
static var flySoundId;
static var goSoundId;
static var illegalSoundId;
static var loseSoundId;
static var millSoundId;
static var placeSoundId;
static var removeSoundId;
static var selectSoundId;
static var winSoundId;
static var isTemporaryMute = false;
static int? drawSoundId;
static int? flySoundId;
static int? goSoundId;
static int? illegalSoundId;
static int? loseSoundId;
static int? millSoundId;
static int? placeSoundId;
static int? removeSoundId;
static int? selectSoundId;
static int? winSoundId;
static bool isTemporaryMute = false;
static Future<void> loadSounds() async {
if (Platform.isWindows) {
debugPrint("[audio] Audio Player does not support Windows.");
return;
}
@ -50,7 +54,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: _soundpool is null.");
debugPrint("[audio] Error: _soundpool is null.");
return;
}
@ -60,7 +64,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: drawSoundId is null.");
debugPrint("[audio] Error: drawSoundId is null.");
return;
}
@ -70,7 +74,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: flySoundId is null.");
debugPrint("[audio] Error: flySoundId is null.");
return;
}
@ -80,7 +84,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: goSoundId is null.");
debugPrint("[audio] Error: goSoundId is null.");
return;
}
@ -90,7 +94,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: illegalSoundId is null.");
debugPrint("[audio] Error: illegalSoundId is null.");
return;
}
@ -100,7 +104,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: loseSoundId is null.");
debugPrint("[audio] Error: loseSoundId is null.");
return;
}
@ -110,7 +114,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: millSoundId is null.");
debugPrint("[audio] Error: millSoundId is null.");
return;
}
@ -120,7 +124,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: placeSoundId is null.");
debugPrint("[audio] Error: placeSoundId is null.");
return;
}
@ -130,7 +134,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: removeSoundId is null.");
debugPrint("[audio] Error: removeSoundId is null.");
return;
}
@ -140,7 +144,7 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: selectSoundId is null.");
debugPrint("[audio] Error: selectSoundId is null.");
return;
}
@ -150,12 +154,12 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: winSoundId is null.");
debugPrint("[audio] Error: winSoundId is null.");
return;
}
}
static Future<void> _playSound(var soundId) async {
static Future<void> _playSound(int? soundId) async {
if (Platform.isWindows) {
return;
}
@ -164,11 +168,11 @@ class Audios {
if (Config.developerMode) {
assert(false);
}
print("[audio] Error: soundId is null.");
debugPrint("[audio] Error: soundId is null.");
return;
}
_alarmSoundStreamId = await _soundpool!.play(await soundId);
_alarmSoundStreamId = await _soundpool!.play(soundId);
}
static Future<void> _stopSound() async {
@ -189,7 +193,7 @@ class Audios {
_soundpool!.dispose();
}
static playTone(var soundId) async {
static Future<void> playTone(int? soundId) async {
Chain.capture(() async {
if (!Config.toneEnabled ||
isTemporaryMute ||
@ -198,7 +202,7 @@ class Audios {
}
if (Platform.isWindows) {
print("audio players is not support Windows.");
debugPrint("audio players is not support Windows.");
return;
}
@ -212,7 +216,7 @@ class Audios {
_playSound(soundId);
} catch (e) {
// Fallback for all errors
print(e);
debugPrint(e.toString());
}
});
}

View File

@ -20,41 +20,47 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/mill/position.dart';
import 'package:sanmill/mill/types.dart';
import 'engine.dart';
import 'package:sanmill/services/engine/engine.dart';
import 'package:sanmill/shared/common/config.dart';
class NativeEngine extends Engine {
static const platform = const MethodChannel('com.calcitem.sanmill/engine');
static const platform = MethodChannel('com.calcitem.sanmill/engine');
bool isActive = false;
@override
Future<void> startup() async {
await platform.invokeMethod('startup');
await waitResponse(['uciok'], sleep: 100, times: 0);
await waitResponse(['uciok']);
}
Future<void> send(String command) async {
print("[engine] send: $command");
debugPrint("[engine] send: $command");
await platform.invokeMethod('send', command);
}
Future<String?> read() async {
return await platform.invokeMethod('read');
return platform.invokeMethod('read');
}
@override
Future<void> shutdown() async {
isActive = false;
await platform.invokeMethod('shutdown');
}
Future<bool?> isReady() async {
return await platform.invokeMethod('isReady');
return platform.invokeMethod('isReady');
}
FutureOr<bool> isThinking() async {
return await platform.invokeMethod('isThinking');
final _isThinking = await platform.invokeMethod<bool>('isThinking');
if (_isThinking is bool) {
return _isThinking;
} else {
throw 'Invalid platform response. Expected a value of type bool';
}
}
@override
@ -68,12 +74,12 @@ class NativeEngine extends Engine {
await send('go');
isActive = true;
} else {
print("[engine] Move now");
debugPrint("[engine] Move now");
}
final response = await waitResponse(['bestmove', 'nobestmove']);
print("[engine] response: $response");
debugPrint("[engine] response: $response");
if (response.startsWith('bestmove')) {
var best = response.substring('bestmove'.length + 1);
@ -91,8 +97,11 @@ class NativeEngine extends Engine {
return EngineResponse('timeout');
}
Future<String> waitResponse(List<String> prefixes,
{sleep = 100, times = 0}) async {
Future<String> waitResponse(
List<String> prefixes, {
int sleep = 100,
int times = 0,
}) async {
var timeLimit = Config.developerMode ? 100 : 6000;
if (Config.moveTime > 0) {
@ -101,9 +110,9 @@ class NativeEngine extends Engine {
}
if (times > timeLimit) {
print("[engine] Timeout. sleep = $sleep, times = $times");
debugPrint("[engine] Timeout. sleep = $sleep, times = $times");
if (Config.developerMode && isActive) {
throw ("Exception: waitResponse timeout.");
throw "Exception: waitResponse timeout.";
}
return '';
}
@ -111,11 +120,11 @@ class NativeEngine extends Engine {
final response = await read();
if (response != null) {
for (var prefix in prefixes) {
for (final prefix in prefixes) {
if (response.startsWith(prefix)) {
return response;
} else {
print("[engine] Unexpected engine response: $response");
debugPrint("[engine] Unexpected engine response: $response");
}
}
}
@ -128,54 +137,89 @@ class NativeEngine extends Engine {
Future<void> stopSearching() async {
isActive = false;
print("[engine] Stop current thinking...");
debugPrint("[engine] Stop current thinking...");
await send('stop');
}
@override
Future<void> setOptions(BuildContext context) async {
if (Config.settingsLoaded == false) {
print("[engine] Settings is not loaded yet, now load settings...");
debugPrint("[engine] Settings is not loaded yet, now load settings...");
await Config.loadSettings();
}
await send('setoption name DeveloperMode value ${Config.developerMode}');
await send('setoption name Algorithm value ${Config.algorithm}');
await send(
'setoption name DrawOnHumanExperience value ${Config.drawOnHumanExperience}');
'setoption name DeveloperMode value ${Config.developerMode}',
);
await send(
'setoption name ConsiderMobility value ${Config.considerMobility}');
await send('setoption name SkillLevel value ${Config.skillLevel}');
await send('setoption name MoveTime value ${Config.moveTime}');
await send('setoption name AiIsLazy value ${Config.aiIsLazy}');
await send('setoption name Shuffling value ${Config.shufflingEnabled}');
await send('setoption name PiecesCount value ${Config.piecesCount}');
await send('setoption name FlyPieceCount value ${Config.flyPieceCount}');
'setoption name Algorithm value ${Config.algorithm}',
);
await send(
'setoption name PiecesAtLeastCount value ${Config.piecesAtLeastCount}');
'setoption name DrawOnHumanExperience value ${Config.drawOnHumanExperience}',
);
await send(
'setoption name HasDiagonalLines value ${Config.hasDiagonalLines}');
'setoption name ConsiderMobility value ${Config.considerMobility}',
);
await send(
'setoption name HasBannedLocations value ${Config.hasBannedLocations}');
'setoption name SkillLevel value ${Config.skillLevel}',
);
await send(
'setoption name MayMoveInPlacingPhase value ${Config.mayMoveInPlacingPhase}');
'setoption name MoveTime value ${Config.moveTime}',
);
await send(
'setoption name IsDefenderMoveFirst value ${Config.isDefenderMoveFirst}');
'setoption name AiIsLazy value ${Config.aiIsLazy}',
);
await send(
'setoption name MayRemoveMultiple value ${Config.mayRemoveMultiple}');
'setoption name Shuffling value ${Config.shufflingEnabled}',
);
await send(
'setoption name MayRemoveFromMillsAlways value ${Config.mayRemoveFromMillsAlways}');
'setoption name PiecesCount value ${Config.piecesCount}',
);
await send(
'setoption name MayOnlyRemoveUnplacedPieceInPlacingPhase value ${Config.mayOnlyRemoveUnplacedPieceInPlacingPhase}');
'setoption name FlyPieceCount value ${Config.flyPieceCount}',
);
await send(
'setoption name IsWhiteLoseButNotDrawWhenBoardFull value ${Config.isWhiteLoseButNotDrawWhenBoardFull}');
'setoption name PiecesAtLeastCount value ${Config.piecesAtLeastCount}',
);
await send(
'setoption name IsLoseButNotChangeSideWhenNoWay value ${Config.isLoseButNotChangeSideWhenNoWay}');
await send('setoption name MayFly value ${Config.mayFly}');
await send('setoption name NMoveRule value ${Config.nMoveRule}');
'setoption name HasDiagonalLines value ${Config.hasDiagonalLines}',
);
await send(
'setoption name EndgameNMoveRule value ${Config.endgameNMoveRule}');
'setoption name HasBannedLocations value ${Config.hasBannedLocations}',
);
await send(
'setoption name ThreefoldRepetitionRule value ${Config.threefoldRepetitionRule}');
'setoption name MayMoveInPlacingPhase value ${Config.mayMoveInPlacingPhase}',
);
await send(
'setoption name IsDefenderMoveFirst value ${Config.isDefenderMoveFirst}',
);
await send(
'setoption name MayRemoveMultiple value ${Config.mayRemoveMultiple}',
);
await send(
'setoption name MayRemoveFromMillsAlways value ${Config.mayRemoveFromMillsAlways}',
);
await send(
'setoption name MayOnlyRemoveUnplacedPieceInPlacingPhase value ${Config.mayOnlyRemoveUnplacedPieceInPlacingPhase}',
);
await send(
'setoption name IsWhiteLoseButNotDrawWhenBoardFull value ${Config.isWhiteLoseButNotDrawWhenBoardFull}',
);
await send(
'setoption name IsLoseButNotChangeSideWhenNoWay value ${Config.isLoseButNotChangeSideWhenNoWay}',
);
await send(
'setoption name MayFly value ${Config.mayFly}',
);
await send(
'setoption name NMoveRule value ${Config.nMoveRule}',
);
await send(
'setoption name EndgameNMoveRule value ${Config.endgameNMoveRule}',
);
await send(
'setoption name ThreefoldRepetitionRule value ${Config.threefoldRepetitionRule}',
);
}
String getPositionFen(Position position) {

View File

@ -16,14 +16,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:sanmill/common/constants.dart';
import 'package:flutter/foundation.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/mill/rule.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/shared/common/constants.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
import 'settings.dart';
class Config {
const Config._();
static bool settingsLoaded = false;
static bool isPrivacyPolicyAccepted = false;
@ -60,7 +62,7 @@ class Config {
static double pointWidth = 10.0;
static double pieceWidth = 0.9;
static double fontSize = 16.0;
static double boardTop = isLargeScreen() ? 75.0 : 36.0;
static double boardTop = isLargeScreen ? 75.0 : 36.0;
static double animationDuration = 0.0;
// Color
@ -87,8 +89,7 @@ class Config {
static int piecesCount = specialCountryAndRegion == "Iran" ? 12 : 9;
static int flyPieceCount = 3;
static int piecesAtLeastCount = 3;
static bool hasDiagonalLines =
specialCountryAndRegion == "Iran" ? true : false;
static bool hasDiagonalLines = specialCountryAndRegion == "Iran";
static bool hasBannedLocations = false;
static bool mayMoveInPlacingPhase = false;
static bool isDefenderMoveFirst = false;
@ -102,124 +103,136 @@ class Config {
static int endgameNMoveRule = 100;
static bool threefoldRepetitionRule = true;
// TODO: use jsonSerializable
static Future<void> loadSettings() async {
print("[config] Loading settings...");
debugPrint("[config] Loading settings...");
final settings = await Settings.instance();
Config.isPrivacyPolicyAccepted =
settings['IsPrivacyPolicyAccepted'] ?? false;
settings['IsPrivacyPolicyAccepted'] as bool? ?? false;
// Preferences
Config.toneEnabled = settings['ToneEnabled'] ?? true;
Config.keepMuteWhenTakingBack = settings['KeepMuteWhenTakingBack'] ?? true;
Config.screenReaderSupport = settings['ScreenReaderSupport'] ?? false;
Config.aiMovesFirst = settings['AiMovesFirst'] ?? false;
Config.aiIsLazy = settings['AiIsLazy'] ?? false;
Config.skillLevel = settings['SkillLevel'] ?? 1;
Config.moveTime = settings['MoveTime'] ?? 1;
Config.isAutoRestart = settings['IsAutoRestart'] ?? false;
Config.isAutoChangeFirstMove = settings['IsAutoChangeFirstMove'] ?? false;
Config.resignIfMostLose = settings['ResignIfMostLose'] ?? false;
Config.shufflingEnabled = settings['ShufflingEnabled'] ?? true;
Config.learnEndgame = settings['LearnEndgame'] ?? false;
Config.openingBook = settings['OpeningBook'] ?? false;
Config.algorithm = settings['Algorithm'] ?? 2;
Config.drawOnHumanExperience = settings['DrawOnHumanExperience'] ?? true;
Config.considerMobility = settings['ConsiderMobility'] ?? true;
Config.developerMode = settings['DeveloperMode'] ?? false;
Config.experimentsEnabled = settings['ExperimentsEnabled'] ?? false;
Config.toneEnabled = settings['ToneEnabled'] as bool? ?? true;
Config.keepMuteWhenTakingBack =
settings['KeepMuteWhenTakingBack'] as bool? ?? true;
Config.screenReaderSupport =
settings['ScreenReaderSupport'] as bool? ?? false;
Config.aiMovesFirst = settings['AiMovesFirst'] as bool? ?? false;
Config.aiIsLazy = settings['AiIsLazy'] as bool? ?? false;
Config.skillLevel = settings['SkillLevel'] as int? ?? 1;
Config.moveTime = settings['MoveTime'] as int? ?? 1;
Config.isAutoRestart = settings['IsAutoRestart'] as bool? ?? false;
Config.isAutoChangeFirstMove =
settings['IsAutoChangeFirstMove'] as bool? ?? false;
Config.resignIfMostLose = settings['ResignIfMostLose'] as bool? ?? false;
Config.shufflingEnabled = settings['ShufflingEnabled'] as bool? ?? true;
Config.learnEndgame = settings['LearnEndgame'] as bool? ?? false;
Config.openingBook = settings['OpeningBook'] as bool? ?? false;
Config.algorithm = settings['Algorithm'] as int? ?? 2;
Config.drawOnHumanExperience =
settings['DrawOnHumanExperience'] as bool? ?? true;
Config.considerMobility = settings['ConsiderMobility'] as bool? ?? true;
Config.developerMode = settings['DeveloperMode'] as bool? ?? false;
Config.experimentsEnabled =
settings['ExperimentsEnabled'] as bool? ?? false;
// Display
Config.languageCode =
settings['LanguageCode'] ?? Constants.defaultLanguageCodeName;
Config.languageCode = settings['LanguageCode'] as String? ??
Constants.defaultLanguageCodeName;
Config.standardNotationEnabled =
settings['StandardNotationEnabled'] ?? true;
settings['StandardNotationEnabled'] as bool? ?? true;
Config.isPieceCountInHandShown =
settings['IsPieceCountInHandShown'] ?? true;
Config.isNotationsShown = settings['IsNotationsShown'] ?? false;
settings['IsPieceCountInHandShown'] as bool? ?? true;
Config.isNotationsShown = settings['IsNotationsShown'] as bool? ?? false;
Config.isHistoryNavigationToolbarShown =
settings['IsHistoryNavigationToolbarShown'] ?? false;
Config.boardBorderLineWidth = settings['BoardBorderLineWidth'] ?? 2;
Config.boardInnerLineWidth = settings['BoardInnerLineWidth'] ?? 2;
Config.pointStyle = settings['PointStyle'] ?? 0;
Config.pointWidth = settings['PointWidth'] ?? 10.0;
Config.pieceWidth = settings['PieceWidth'] ?? 0.9;
Config.fontSize = settings['FontSize'] ?? 16.0;
Config.boardTop = settings['BoardTop'] ?? (isLargeScreen() ? 75 : 36);
Config.animationDuration = settings['AnimationDuration'] ?? 0;
settings['IsHistoryNavigationToolbarShown'] as bool? ?? false;
Config.boardBorderLineWidth =
settings['BoardBorderLineWidth'] as double? ?? 2.0;
Config.boardInnerLineWidth =
settings['BoardInnerLineWidth'] as double? ?? 2.0;
Config.pointStyle = settings['PointStyle'] as int? ?? 0;
Config.pointWidth = settings['PointWidth'] as double? ?? 10.0;
Config.pieceWidth = settings['PieceWidth'] as double? ?? 0.9;
Config.fontSize = settings['FontSize'] as double? ?? 16.0;
Config.boardTop =
settings['BoardTop'] as double? ?? (isLargeScreen ? 75.0 : 36.0);
Config.animationDuration = settings['AnimationDuration'] as double? ?? 0.0;
// Color
Config.boardLineColor =
settings['BoardLineColor'] ?? AppTheme.boardLineColor.value;
Config.darkBackgroundColor =
settings['DarkBackgroundColor'] ?? AppTheme.darkBackgroundColor.value;
Config.boardBackgroundColor =
settings['BoardBackgroundColor'] ?? AppTheme.boardBackgroundColor.value;
settings['BoardLineColor'] as int? ?? AppTheme.boardLineColor.value;
Config.darkBackgroundColor = settings['DarkBackgroundColor'] as int? ??
AppTheme.darkBackgroundColor.value;
Config.boardBackgroundColor = settings['BoardBackgroundColor'] as int? ??
AppTheme.boardBackgroundColor.value;
Config.whitePieceColor =
settings['WhitePieceColor'] ?? AppTheme.whitePieceColor.value;
settings['WhitePieceColor'] as int? ?? AppTheme.whitePieceColor.value;
Config.blackPieceColor =
settings['BlackPieceColor'] ?? AppTheme.blackPieceColor.value;
Config.pieceHighlightColor =
settings['PieceHighlightColor'] ?? AppTheme.pieceHighlightColor.value;
settings['BlackPieceColor'] as int? ?? AppTheme.blackPieceColor.value;
Config.pieceHighlightColor = settings['PieceHighlightColor'] as int? ??
AppTheme.pieceHighlightColor.value;
Config.messageColor =
settings['MessageColor'] ?? AppTheme.messageColor.value;
Config.drawerColor = settings['DrawerColor'] ?? AppTheme.drawerColor.value;
Config.drawerBackgroundColor = settings['DrawerBackgroundColor'] ??
settings['MessageColor'] as int? ?? AppTheme.messageColor.value;
Config.drawerColor =
settings['DrawerColor'] as int? ?? AppTheme.drawerColor.value;
Config.drawerBackgroundColor = settings['DrawerBackgroundColor'] as int? ??
AppTheme.drawerBackgroundColor.value;
Config.drawerTextColor =
settings['DrawerTextColor'] ?? AppTheme.drawerTextColor.value;
Config.drawerHighlightItemColor = settings['DrawerHighlightItemColor'] ??
AppTheme.drawerHighlightItemColor.value;
settings['DrawerTextColor'] as int? ?? AppTheme.drawerTextColor.value;
Config.drawerHighlightItemColor =
settings['DrawerHighlightItemColor'] as int? ??
AppTheme.drawerHighlightItemColor.value;
Config.mainToolbarBackgroundColor =
settings['MainToolbarBackgroundColor'] ??
settings['MainToolbarBackgroundColor'] as int? ??
AppTheme.mainToolbarBackgroundColor.value;
Config.mainToolbarIconColor =
settings['MainToolbarIconColor'] ?? AppTheme.mainToolbarIconColor.value;
Config.mainToolbarIconColor = settings['MainToolbarIconColor'] as int? ??
AppTheme.mainToolbarIconColor.value;
Config.navigationToolbarBackgroundColor =
settings['NavigationToolbarBackgroundColor'] ??
settings['NavigationToolbarBackgroundColor'] as int? ??
AppTheme.navigationToolbarBackgroundColor.value;
Config.navigationToolbarIconColor =
settings['NavigationToolbarIconColor'] ??
settings['NavigationToolbarIconColor'] as int? ??
AppTheme.navigationToolbarIconColor.value;
// Rules
rule.piecesCount = Config.piecesCount =
settings['PiecesCount'] ?? (specialCountryAndRegion == "Iran" ? 12 : 9);
rule.flyPieceCount = Config.flyPieceCount = settings['FlyPieceCount'] ?? 3;
rule.piecesCount = Config.piecesCount = settings['PiecesCount'] as int? ??
(specialCountryAndRegion == "Iran" ? 12 : 9);
rule.flyPieceCount =
Config.flyPieceCount = settings['FlyPieceCount'] as int? ?? 3;
rule.piecesAtLeastCount =
Config.piecesAtLeastCount = settings['PiecesAtLeastCount'] ?? 3;
Config.piecesAtLeastCount = settings['PiecesAtLeastCount'] as int? ?? 3;
rule.hasDiagonalLines = Config.hasDiagonalLines =
settings['HasDiagonalLines'] ??
(specialCountryAndRegion == "Iran" ? true : false);
rule.hasBannedLocations =
Config.hasBannedLocations = settings['HasBannedLocations'] ?? false;
settings['HasDiagonalLines'] as bool? ??
(specialCountryAndRegion == "Iran");
rule.hasBannedLocations = Config.hasBannedLocations =
settings['HasBannedLocations'] as bool? ?? false;
rule.mayMoveInPlacingPhase = Config.mayMoveInPlacingPhase =
settings['MayMoveInPlacingPhase'] ?? false;
rule.isDefenderMoveFirst =
Config.isDefenderMoveFirst = settings['IsDefenderMoveFirst'] ?? false;
rule.mayRemoveMultiple =
Config.mayRemoveMultiple = settings['MayRemoveMultiple'] ?? false;
settings['MayMoveInPlacingPhase'] as bool? ?? false;
rule.isDefenderMoveFirst = Config.isDefenderMoveFirst =
settings['IsDefenderMoveFirst'] as bool? ?? false;
rule.mayRemoveMultiple = Config.mayRemoveMultiple =
settings['MayRemoveMultiple'] as bool? ?? false;
rule.mayRemoveFromMillsAlways = Config.mayRemoveFromMillsAlways =
settings['MayRemoveFromMillsAlways'] ?? false;
rule.mayOnlyRemoveUnplacedPieceInPlacingPhase =
Config.mayOnlyRemoveUnplacedPieceInPlacingPhase =
settings['MayOnlyRemoveUnplacedPieceInPlacingPhase'] ?? false;
settings['MayRemoveFromMillsAlways'] as bool? ?? false;
rule.mayOnlyRemoveUnplacedPieceInPlacingPhase = Config
.mayOnlyRemoveUnplacedPieceInPlacingPhase =
settings['MayOnlyRemoveUnplacedPieceInPlacingPhase'] as bool? ?? false;
rule.isWhiteLoseButNotDrawWhenBoardFull =
Config.isWhiteLoseButNotDrawWhenBoardFull =
settings['IsWhiteLoseButNotDrawWhenBoardFull'] ?? true;
settings['IsWhiteLoseButNotDrawWhenBoardFull'] as bool? ?? true;
rule.isLoseButNotChangeSideWhenNoWay =
Config.isLoseButNotChangeSideWhenNoWay =
settings['IsLoseButNotChangeSideWhenNoWay'] ?? true;
rule.mayFly = Config.mayFly = settings['MayFly'] ?? true;
rule.nMoveRule = Config.nMoveRule = settings['NMoveRule'] ?? 100;
settings['IsLoseButNotChangeSideWhenNoWay'] as bool? ?? true;
rule.mayFly = Config.mayFly = settings['MayFly'] as bool? ?? true;
rule.nMoveRule = Config.nMoveRule = settings['NMoveRule'] as int? ?? 100;
rule.endgameNMoveRule =
Config.endgameNMoveRule = settings['EndgameNMoveRule'] ?? 100;
Config.endgameNMoveRule = settings['EndgameNMoveRule'] as int? ?? 100;
rule.threefoldRepetitionRule = Config.threefoldRepetitionRule =
settings['ThreefoldRepetitionRule'] ?? true;
settings['ThreefoldRepetitionRule'] as bool? ?? true;
settingsLoaded = true;
print("[config] Loading settings done!");
debugPrint("[config] Loading settings done!");
}
static Future<bool> save() async {

View File

@ -19,6 +19,7 @@
import 'dart:ui';
class Constants {
const Constants._();
static String appName = "Mill";
static String authorAccount = "calcitem";
static String projectName = "Sanmill";
@ -53,8 +54,8 @@ class Constants {
static String githubEulaURL = "$githubRepoWiKiURL/EULA";
static String giteeEulaURL = "$giteeRepoWiKiURL/EULA_zh";
static String githubSourceCodeURL = "$githubRepoURL";
static String giteeSourceCodeURL = "$giteeRepoURL";
static String githubSourceCodeURL = githubRepoURL;
static String giteeSourceCodeURL = giteeRepoURL;
static String githubThirdPartyNoticesURL =
"$githubRepoWiKiURL/third-party_notices";
@ -77,10 +78,6 @@ class Constants {
static final windowAspectRatio = windowHeight / windowWidth;
}
bool isSmallScreen() {
return Constants.windowHeight <= 800;
}
bool get isSmallScreen => Constants.windowHeight <= 800;
bool isLargeScreen() {
return !isSmallScreen();
}
bool get isLargeScreen => !isSmallScreen;

View File

@ -19,8 +19,9 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/shared/common/constants.dart';
class Settings {
static final settingsFileName = Constants.settingsFilename;
@ -29,19 +30,20 @@ class Settings {
late File _file;
Map<String, dynamic>? _values = {};
static instance() async {
// TODO: add constructor
static Future<Settings> instance() async {
if (_instance == null) {
_instance = Settings();
await _instance!._load(settingsFileName);
print("[settings] $settingsFileName loaded.");
debugPrint("[settings] $settingsFileName loaded.");
}
return _instance;
return _instance!;
}
operator [](String key) => _values![key];
dynamic operator [](String key) => _values![key];
operator []=(String key, dynamic value) => _values![key] = value;
void operator []=(String key, dynamic value) => _values![key] = value;
Future<bool> commit() async {
_file.create(recursive: true);
@ -57,18 +59,18 @@ class Settings {
Future<bool> _load(String fileName) async {
// TODO: main() ExternalStorage
// var docDir = await getExternalStorageDirectory();
var docDir = await getApplicationDocumentsDirectory();
final docDir = await getApplicationDocumentsDirectory();
_file = File('${docDir.path}/$fileName');
print("[settings] Loading $_file ...");
debugPrint("[settings] Loading $_file ...");
try {
final contents = await _file.readAsString();
_values = jsonDecode(contents);
print(_values);
_values = jsonDecode(contents) as Map<String, dynamic>?;
debugPrint(_values.toString());
} catch (e) {
print(e);
debugPrint(e.toString());
return false;
}
@ -76,13 +78,13 @@ class Settings {
}
Future<void> restore() async {
print("[settings] Restoring Settings...");
debugPrint("[settings] Restoring Settings...");
if (_file.existsSync()) {
_file.deleteSync();
print("[settings] $_file deleted");
debugPrint("[settings] $_file deleted");
} else {
print("[settings] $_file does not exist");
debugPrint("[settings] $_file does not exist");
}
}
}

View File

@ -23,32 +23,36 @@ import 'package:devicelocale/devicelocale.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/common/constants.dart';
import 'package:url_launcher/url_launcher.dart';
int _counter = 0;
Timer? _timer;
void startTimer(var counter, var events) {
void startTimer(int counter, StreamController<int> events) {
_counter = counter;
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
(_counter > 0) ? _counter-- : _timer!.cancel();
events.add(_counter);
});
}
void showCountdownDialog(
BuildContext ctx, var seconds, var events, void fun()) {
var alert = AlertDialog(
BuildContext ctx,
int seconds,
StreamController<int> events,
void Function() fun,
) {
final alert = AlertDialog(
content: StreamBuilder<int>(
stream: events.stream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print("Count down: " + snapshot.data.toString());
debugPrint("Count down: ${snapshot.data}");
if (snapshot.data == 0) {
fun();
@ -57,32 +61,28 @@ void showCountdownDialog(
} else {}
}
return Container(
return SizedBox(
height: 128,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
snapshot.data != null ? '${snapshot.data.toString()}' : "10",
style: TextStyle(fontSize: 64),
),
SizedBox(
height: 20,
snapshot.data != null ? snapshot.data.toString() : "10",
style: const TextStyle(fontSize: 64),
),
const SizedBox(height: 20),
InkWell(
onTap: () {
Navigator.of(context).pop();
Navigator.pop(context);
},
child: Container(
child: Center(
child: Text(
child: Center(
child: Text(
S.of(ctx).cancel,
style: TextStyle(
color: Colors.black,
fontSize: Config.fontSize,
fontWeight: FontWeight.bold,
),
)),
),
),
),
],
@ -105,16 +105,19 @@ void showCountdownDialog(
class _LinkTextSpan extends TextSpan {
_LinkTextSpan({TextStyle? style, required String url, String? text})
: super(
style: style,
text: text ?? url,
recognizer: TapGestureRecognizer()
..onTap = () {
launch(url, forceSafariVC: false);
});
style: style,
text: text ?? url,
recognizer: TapGestureRecognizer()
..onTap = () {
launch(url, forceSafariVC: false);
},
);
}
showPrivacyDialog(
BuildContext context, setPrivacyPolicyAccepted(bool value)) async {
Future<void> showPrivacyDialog(
BuildContext context,
Function(bool value) setPrivacyPolicyAccepted,
) async {
String? locale = "en_US";
late String eulaURL;
late String privacyPolicyURL;
@ -122,7 +125,7 @@ showPrivacyDialog(
locale = await Devicelocale.currentLocale;
}
print("[about] local = $locale");
debugPrint("[about] local = $locale");
if (locale != null && locale.startsWith("zh_")) {
eulaURL = Constants.giteeEulaURL;
privacyPolicyURL = Constants.giteePrivacyPolicyURL;
@ -133,8 +136,8 @@ showPrivacyDialog(
final ThemeData themeData = Theme.of(context);
final TextStyle? aboutTextStyle = themeData.textTheme.bodyText1;
final TextStyle linkStyle =
themeData.textTheme.bodyText1!.copyWith(color: themeData.accentColor);
final TextStyle linkStyle = themeData.textTheme.bodyText1!
.copyWith(color: themeData.colorScheme.secondary);
showDialog(
context: context,
@ -176,18 +179,17 @@ showPrivacyDialog(
child: Text(S.of(context).accept),
onPressed: () {
setPrivacyPolicyAccepted(true);
Navigator.of(context).pop();
Navigator.pop(context);
},
),
Platform.isAndroid
? TextButton(
child: Text(S.of(context).exit),
onPressed: () {
setPrivacyPolicyAccepted(false);
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
},
)
: Container(height: 0.0, width: 0.0),
if (Platform.isAndroid)
TextButton(
child: Text(S.of(context).exit),
onPressed: () {
setPrivacyPolicyAccepted(false);
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
},
),
],
),
);

View File

@ -16,95 +16,96 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/home_drawer.dart';
part of 'package:sanmill/screens/navigation_home_screen.dart';
class DrawerUserController extends StatefulWidget {
const DrawerUserController({
class DrawerController extends StatefulWidget {
const DrawerController({
Key? key,
this.drawerWidth = AppTheme.drawerWidth,
this.onDrawerCall,
this.screenView,
required this.onDrawerCall,
required this.screenView,
this.animatedIconData = AnimatedIcons.arrow_menu,
this.menuView,
this.drawerIsOpen,
this.screenIndex,
required this.screenIndex,
}) : super(key: key);
final double drawerWidth;
final Function(DrawerIndex)? onDrawerCall;
final Widget? screenView;
final Function(DrawerIndex) onDrawerCall;
final Widget screenView;
final Function(bool)? drawerIsOpen;
final AnimatedIconData animatedIconData;
final Widget? menuView;
final DrawerIndex? screenIndex;
final DrawerIndex screenIndex;
@override
_DrawerUserControllerState createState() => _DrawerUserControllerState();
_DrawerControllerState createState() => _DrawerControllerState();
}
class _DrawerUserControllerState extends State<DrawerUserController>
class _DrawerControllerState extends State<DrawerController>
with TickerProviderStateMixin {
late ScrollController scrollController;
late AnimationController iconAnimationController;
late AnimationController animationController;
late final ScrollController scrollController;
late final AnimationController iconAnimationController;
late final AnimationController animationController;
double scrollOffset = 0.0;
@override
void initState() {
animationController = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
duration: const Duration(seconds: 2),
vsync: this,
);
iconAnimationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 0));
iconAnimationController =
AnimationController(vsync: this, duration: Duration.zero);
iconAnimationController
..animateTo(1.0,
duration: const Duration(milliseconds: 0),
curve: Curves.fastOutSlowIn);
iconAnimationController.animateTo(
1.0,
duration: Duration.zero,
curve: Curves.fastOutSlowIn,
);
scrollController =
ScrollController(initialScrollOffset: widget.drawerWidth);
scrollController
..addListener(() {
if (scrollController.offset <= 0) {
if (scrollOffset != 1.0) {
setState(() {
scrollOffset = 1.0;
try {
widget.drawerIsOpen!(true);
} catch (_) {}
});
}
iconAnimationController.animateTo(0.0,
duration: const Duration(milliseconds: 0),
curve: Curves.fastOutSlowIn);
} else if (scrollController.offset > 0 &&
scrollController.offset < widget.drawerWidth.floor()) {
iconAnimationController.animateTo(
(scrollController.offset * 100 / (widget.drawerWidth)) / 100,
duration: const Duration(milliseconds: 0),
curve: Curves.fastOutSlowIn);
} else {
if (scrollOffset != 0.0) {
setState(() {
scrollOffset = 0.0;
try {
widget.drawerIsOpen!(false);
} catch (_) {}
});
}
iconAnimationController.animateTo(1.0,
duration: const Duration(milliseconds: 0),
curve: Curves.fastOutSlowIn);
scrollController.addListener(() {
if (scrollController.offset <= 0) {
if (scrollOffset != 1.0) {
setState(() {
scrollOffset = 1.0;
try {
widget.drawerIsOpen!(true);
} catch (_) {}
});
}
});
iconAnimationController.animateTo(
0.0,
duration: Duration.zero,
curve: Curves.fastOutSlowIn,
);
} else if (scrollController.offset < widget.drawerWidth.floor()) {
iconAnimationController.animateTo(
(scrollController.offset * 100 / (widget.drawerWidth)) / 100,
duration: Duration.zero,
curve: Curves.fastOutSlowIn,
);
} else {
if (scrollOffset != 0.0) {
setState(() {
scrollOffset = 0.0;
try {
widget.drawerIsOpen!(false);
} catch (_) {}
});
}
iconAnimationController.animateTo(
1.0,
duration: Duration.zero,
curve: Curves.fastOutSlowIn,
);
}
});
WidgetsBinding.instance!.addPostFrameCallback((_) => getInitState());
super.initState();
@ -119,22 +120,23 @@ class _DrawerUserControllerState extends State<DrawerUserController>
@override
Widget build(BuildContext context) {
bool ltr = getBidirectionality(context) == Bidirectionality.leftToRight;
final bool ltr =
getBidirectionality(context) == Bidirectionality.leftToRight;
// this just menu and arrow icon animation
var inkWell = InkWell(
final inkWell = InkWell(
borderRadius: BorderRadius.circular(AppBar().preferredSize.height),
child: Center(
// if you use your own menu view UI you add form initialization
child: widget.menuView != null
? widget.menuView
: Semantics(
label: S.of(context).mainMenu,
child: AnimatedIcon(
icon: widget.animatedIconData,
color: AppTheme.drawerAnimationIconColor,
progress: iconAnimationController),
child: widget.menuView ??
Semantics(
label: S.of(context).mainMenu,
child: AnimatedIcon(
icon: widget.animatedIconData,
color: AppTheme.drawerAnimationIconColor,
progress: iconAnimationController,
),
),
),
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
@ -142,7 +144,55 @@ class _DrawerUserControllerState extends State<DrawerUserController>
},
);
var animatedBuilder = AnimatedBuilder(
final List<DrawerListItem> drawerItems = [
DrawerListItem(
index: DrawerIndex.humanVsAi,
title: S.of(context).humanVsAi,
icon: const Icon(FluentIcons.person_24_regular),
),
DrawerListItem(
index: DrawerIndex.humanVsHuman,
title: S.of(context).humanVsHuman,
icon: const Icon(FluentIcons.people_24_regular),
),
DrawerListItem(
index: DrawerIndex.aiVsAi,
title: S.of(context).aiVsAi,
icon: const Icon(FluentIcons.bot_24_regular),
),
DrawerListItem(
index: DrawerIndex.preferences,
title: S.of(context).preferences,
icon: const Icon(FluentIcons.options_24_regular),
),
DrawerListItem(
index: DrawerIndex.ruleSettings,
title: S.of(context).ruleSettings,
icon: const Icon(FluentIcons.task_list_ltr_24_regular),
),
DrawerListItem(
index: DrawerIndex.personalization,
title: S.of(context).personalization,
icon: const Icon(FluentIcons.design_ideas_24_regular),
),
DrawerListItem(
index: DrawerIndex.feedback,
title: S.of(context).feedback,
icon: const Icon(FluentIcons.chat_warning_24_regular),
),
DrawerListItem(
index: DrawerIndex.Help,
title: S.of(context).help,
icon: const Icon(FluentIcons.question_circle_24_regular),
),
DrawerListItem(
index: DrawerIndex.About,
title: S.of(context).about,
icon: const Icon(FluentIcons.info_24_regular),
),
];
final animatedBuilder = AnimatedBuilder(
animation: iconAnimationController,
builder: (BuildContext context, Widget? child) {
return Transform(
@ -151,16 +201,15 @@ class _DrawerUserControllerState extends State<DrawerUserController>
transform:
Matrix4.translationValues(scrollController.offset, 0.0, 0.0),
child: HomeDrawer(
screenIndex: widget.screenIndex == null
? DrawerIndex.humanVsAi
: widget.screenIndex,
screenIndex: widget.screenIndex,
iconAnimationController: iconAnimationController,
callBackIndex: (DrawerIndex? indexType) {
callBackIndex: (DrawerIndex indexType) {
onDrawerClick();
try {
widget.onDrawerCall!(indexType!);
} catch (e) {}
widget.onDrawerCall(indexType);
} catch (_) {}
},
items: drawerItems,
),
);
},
@ -172,7 +221,7 @@ class _DrawerUserControllerState extends State<DrawerUserController>
tapOffset = 10; // TODO: WAR
}
var stack = Stack(
final stack = Stack(
children: <Widget>[
// this IgnorePointer we use as touch(user Interface) widget.screen View,
// for example scrolloffset == 1
@ -186,16 +235,15 @@ class _DrawerUserControllerState extends State<DrawerUserController>
// tap on a few home screen area and close the drawer
if (scrollOffset == 1.0)
InkWell(
onTap: () {
onDrawerClick();
},
onTap: onDrawerClick,
),
Padding(
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + tapOffset, left: 0),
top: MediaQuery.of(context).padding.top + tapOffset,
),
child: SizedBox(
width: AppBar().preferredSize.height,
height: AppBar().preferredSize.height,
width: kToolbarHeight,
height: kToolbarHeight,
child: Material(
color: Colors.transparent,
child: inkWell,
@ -205,7 +253,7 @@ class _DrawerUserControllerState extends State<DrawerUserController>
],
);
var row = Row(
final row = Row(
children: <Widget>[
SizedBox(
width: widget.drawerWidth,
@ -224,7 +272,9 @@ class _DrawerUserControllerState extends State<DrawerUserController>
color: Color(Config.drawerColor),
boxShadow: <BoxShadow>[
BoxShadow(
color: AppTheme.drawerBoxerShadowColor, blurRadius: 24),
color: AppTheme.drawerBoxerShadowColor,
blurRadius: 24,
),
],
),
child: stack,
@ -233,9 +283,9 @@ class _DrawerUserControllerState extends State<DrawerUserController>
],
);
return Scaffold(
backgroundColor: Color(Config.drawerColor),
body: SingleChildScrollView(
return Material(
color: Color(Config.drawerColor),
child: SingleChildScrollView(
controller: scrollController,
scrollDirection: Axis.horizontal,
physics: const PageScrollPhysics(parent: ClampingScrollPhysics()),

View File

@ -17,7 +17,7 @@
*/
import 'package:flutter/material.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
class ListItemDivider extends StatelessWidget {
const ListItemDivider({
@ -26,10 +26,11 @@ class ListItemDivider extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
width: double.infinity,
return Divider(
indent: 16,
endIndent: 16,
height: 1.0,
thickness: 1.0,
color: AppTheme.listItemDividerColor,
);
}

View File

@ -16,14 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/mill/game.dart';
import 'package:sanmill/mill/types.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/game_page.dart';
import 'painter_base.dart';
part of 'package:sanmill/screens/game_page/game_page.dart';
class BoardPainter extends PiecesBasePainter {
BoardPainter({required double width}) : super(width: width);
@ -45,7 +38,7 @@ class BoardPainter extends PiecesBasePainter {
return false;
}
static doPaint(
static void doPaint(
Canvas canvas,
Paint paint,
double gridWidth,
@ -56,47 +49,54 @@ class BoardPainter extends PiecesBasePainter {
paint.color = Color(Config.boardLineColor);
paint.style = PaintingStyle.stroke;
var left = offsetX;
var top = offsetY;
final left = offsetX;
final top = offsetY;
paint.strokeWidth = Config.boardBorderLineWidth;
if (Config.isPieceCountInHandShown) {
var pieceInHandCount =
Game.instance.position.pieceInHandCount[PieceColor.black];
gameInstance.position.pieceInHandCount[PieceColor.black];
if (Game.instance.position.pieceOnBoardCount[PieceColor.white] == 0 &&
Game.instance.position.pieceOnBoardCount[PieceColor.black] == 0) {
if (gameInstance.position.pieceOnBoardCount[PieceColor.white] == 0 &&
gameInstance.position.pieceOnBoardCount[PieceColor.black] == 0) {
pieceInHandCount = Config.piecesCount;
}
var pieceInHandCountStr = "";
if (Game.instance.position.phase == Phase.placing) {
if (gameInstance.position.phase == Phase.placing) {
pieceInHandCountStr = pieceInHandCount.toString();
}
TextSpan textSpan = TextSpan(
style: TextStyle(
fontSize: 48, color: Color(Config.boardLineColor)), // TODO
text: pieceInHandCountStr);
final TextSpan textSpan = TextSpan(
style: TextStyle(
fontSize: 48,
color: Color(Config.boardLineColor),
), // TODO
text: pieceInHandCountStr,
);
TextPainter textPainter = TextPainter(
text: textSpan,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr);
final TextPainter textPainter = TextPainter(
text: textSpan,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(left + squareWidth * 3 - textPainter.width / 2,
top + squareWidth * 3 - textPainter.height / 2));
canvas,
Offset(
left + squareWidth * 3 - textPainter.width / 2,
top + squareWidth * 3 - textPainter.height / 2,
),
);
}
if (Config.isNotationsShown) {
String verticalNotations = "abcdefg";
String horizontalNotations = "7654321";
const String verticalNotations = "abcdefg";
const String horizontalNotations = "7654321";
String notationV = "";
String notationH = "";
@ -104,25 +104,29 @@ class BoardPainter extends PiecesBasePainter {
notationV = verticalNotations[i];
notationH = horizontalNotations[i];
TextSpan notationSpanV = TextSpan(
style:
TextStyle(fontSize: 20, color: AppTheme.boardLineColor), // TODO
final TextSpan notationSpanV = TextSpan(
style: TextStyle(
fontSize: 20,
color: AppTheme.boardLineColor,
), // TODO
text: notationV,
);
TextSpan notationSpanH = TextSpan(
style:
TextStyle(fontSize: 20, color: AppTheme.boardLineColor), // TODO
final TextSpan notationSpanH = TextSpan(
style: TextStyle(
fontSize: 20,
color: AppTheme.boardLineColor,
), // TODO
text: notationH,
);
TextPainter notationPainterV = TextPainter(
final TextPainter notationPainterV = TextPainter(
text: notationSpanV,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
TextPainter notationPainterH = TextPainter(
final TextPainter notationPainterH = TextPainter(
text: notationSpanH,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
@ -131,37 +135,45 @@ class BoardPainter extends PiecesBasePainter {
notationPainterV.layout();
notationPainterH.layout();
var offset = (boardWidth - squareWidth * 6) / 4;
final offset = (boardWidth - squareWidth * 6) / 4;
/* Show notations "a b c d e f" on board */
if (Config.developerMode) {
notationPainterV.paint(
canvas,
Offset(left + squareWidth * i - notationPainterV.width / 2,
top - offset - notationPainterV.height / 2),
Offset(
left + squareWidth * i - notationPainterV.width / 2,
top - offset - notationPainterV.height / 2,
),
);
}
notationPainterV.paint(
canvas,
Offset(left + squareWidth * i - notationPainterV.width / 2,
top + squareWidth * 6 + offset - notationPainterV.height / 2),
Offset(
left + squareWidth * i - notationPainterV.width / 2,
top + squareWidth * 6 + offset - notationPainterV.height / 2,
),
);
/* Show notations "1 2 3 4 5 6 7" on board */
notationPainterH.paint(
canvas,
Offset(left - offset - notationPainterH.width / 2,
top + squareWidth * i - notationPainterH.height / 2),
Offset(
left - offset - notationPainterH.width / 2,
top + squareWidth * i - notationPainterH.height / 2,
),
);
if (Config.developerMode) {
notationPainterH.paint(
canvas,
Offset(left + squareWidth * 6 + offset - notationPainterH.width / 2,
top + squareWidth * i - notationPainterH.height / 2),
Offset(
left + squareWidth * 6 + offset - notationPainterH.width / 2,
top + squareWidth * i - notationPainterH.height / 2,
),
);
}
}
@ -174,19 +186,27 @@ class BoardPainter extends PiecesBasePainter {
);
paint.strokeWidth = Config.boardInnerLineWidth;
double bias = paint.strokeWidth / 2;
final double bias = paint.strokeWidth / 2;
// File B
canvas.drawRect(
Rect.fromLTWH(left + squareWidth * 1, top + squareWidth * 1,
squareWidth * 4, squareWidth * 4),
Rect.fromLTWH(
left + squareWidth * 1,
top + squareWidth * 1,
squareWidth * 4,
squareWidth * 4,
),
paint,
);
// File A
canvas.drawRect(
Rect.fromLTWH(left + squareWidth * 2, top + squareWidth * 2,
squareWidth * 2, squareWidth * 2),
Rect.fromLTWH(
left + squareWidth * 2,
top + squareWidth * 2,
squareWidth * 2,
squareWidth * 2,
),
paint,
);
@ -226,9 +246,9 @@ class BoardPainter extends PiecesBasePainter {
paint.style = PaintingStyle.stroke; // TODO: WIP
}
double pointRadius = Config.pointWidth;
final double pointRadius = Config.pointWidth;
var points = [
final points = [
[0, 0],
[0, 3],
[0, 6],
@ -255,11 +275,13 @@ class BoardPainter extends PiecesBasePainter {
[6, 6],
];
points.forEach((point) => canvas.drawCircle(
Offset(left + squareWidth * point[0], top + squareWidth * point[1]),
pointRadius,
paint,
));
for (final point in points) {
canvas.drawCircle(
Offset(left + squareWidth * point[0], top + squareWidth * point[1]),
pointRadius,
paint,
);
}
}
if (!Config.hasDiagonalLines) {

View File

@ -16,17 +16,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:sanmill/style/app_theme.dart';
part of 'package:sanmill/screens/game_page/game_page.dart';
abstract class PiecesBasePainter extends CustomPainter {
final double width;
final thePaint = Paint();
final gridWidth;
final squareWidth;
final double gridWidth;
final double squareWidth;
PiecesBasePainter({required this.width})
: gridWidth = (width - AppTheme.boardPadding * 2),
: gridWidth = width - AppTheme.boardPadding * 2,
squareWidth = (width - AppTheme.boardPadding * 2) / 7;
}

View File

@ -16,32 +16,29 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/mill/position.dart';
import 'package:sanmill/mill/types.dart';
import 'package:sanmill/style/app_theme.dart';
import 'painter_base.dart';
part of 'package:sanmill/screens/game_page/game_page.dart';
class PiecePaintParam {
// TODO: null-safety
final String? piece;
final Offset? pos;
final bool? animated;
PiecePaintParam({this.piece, this.pos, this.animated});
final String piece;
final Offset pos;
final bool animated;
PiecePaintParam({
required this.piece,
required this.pos,
required this.animated,
});
}
class PiecesPainter extends PiecesBasePainter {
final Position? position;
final int? focusIndex, blurIndex;
final animationValue;
final int? focusIndex;
final int? blurIndex;
final double animationValue;
// TODO: null-safety
int? pointStyle = 0;
double? pointWidth = 10.0;
double? pieceWidth = 0.0;
double? animatedPieceWidth = 0.0;
int pointStyle = 0;
double pointWidth = 10.0;
double pieceWidth = 0.0;
double animatedPieceWidth = 0.0;
PiecesPainter({
required double width,
@ -80,7 +77,7 @@ class PiecesPainter extends PiecesBasePainter {
return true;
}
static doPaint(
static void doPaint(
Canvas canvas,
Paint paint, {
Position? position,
@ -102,8 +99,7 @@ class PiecesPainter extends PiecesBasePainter {
final shadowPath = Path();
final piecesToDraw = <PiecePaintParam>[];
// TODO: null-safety
Color? blurPositionColor;
late Color blurPositionColor;
Color focusPositionColor;
// Draw pieces on board
@ -114,8 +110,9 @@ class PiecesPainter extends PiecesBasePainter {
if (piece == Piece.noPiece) continue;
var pos = Offset(left! + squareWidth! * col, top! + squareWidth * row);
var animated = (focusIndex == index);
final pos =
Offset(left! + squareWidth! * col, top! + squareWidth * row);
final animated = focusIndex == index;
piecesToDraw
.add(PiecePaintParam(piece: piece, pos: pos, animated: animated));
@ -139,26 +136,26 @@ class PiecesPainter extends PiecesBasePainter {
);
*/
piecesToDraw.forEach((pps) {
var pieceRadius = pieceWidth! / 2;
var pieceInnerRadius = pieceRadius * 0.99;
for (final pps in piecesToDraw) {
final pieceRadius = pieceWidth! / 2;
final pieceInnerRadius = pieceRadius * 0.99;
var animatedPieceRadius = animatedPieceWidth! / 2;
var animatedPieceInnerRadius = animatedPieceRadius * 0.99;
final animatedPieceRadius = animatedPieceWidth! / 2;
final animatedPieceInnerRadius = animatedPieceRadius * 0.99;
// Draw Border of Piece
switch (pps.piece) {
case Piece.whiteStone:
paint.color = AppTheme.whitePieceBorderColor;
canvas.drawCircle(
pps.pos!,
pps.animated! ? animatedPieceRadius : pieceRadius,
pps.pos,
pps.animated ? animatedPieceRadius : pieceRadius,
paint,
);
paint.color = Color(Config.whitePieceColor);
canvas.drawCircle(
pps.pos!,
pps.animated! ? animatedPieceInnerRadius : pieceInnerRadius,
pps.pos,
pps.animated ? animatedPieceInnerRadius : pieceInnerRadius,
paint,
);
blurPositionColor = Color(Config.whitePieceColor).withOpacity(0.1);
@ -166,14 +163,14 @@ class PiecesPainter extends PiecesBasePainter {
case Piece.blackStone:
paint.color = AppTheme.blackPieceBorderColor;
canvas.drawCircle(
pps.pos!,
pps.animated! ? animatedPieceRadius : pieceRadius,
pps.pos,
pps.animated ? animatedPieceRadius : pieceRadius,
paint,
);
paint.color = Color(Config.blackPieceColor);
canvas.drawCircle(
pps.pos!,
pps.animated! ? animatedPieceInnerRadius : pieceInnerRadius,
pps.pos,
pps.animated ? animatedPieceInnerRadius : pieceInnerRadius,
paint,
);
blurPositionColor = Color(Config.blackPieceColor).withOpacity(0.1);
@ -185,13 +182,14 @@ class PiecesPainter extends PiecesBasePainter {
assert(false);
break;
}
});
}
// draw focus and blur position
if (focusIndex != invalidIndex) {
final int row = focusIndex! ~/ 7, column = focusIndex % 7;
final int row = focusIndex! ~/ 7;
final int column = focusIndex % 7;
if (focusIndex != invalidIndex) {
/*
focusPositionColor = Color.fromARGB(
(Color(Config.whitePieceColor).alpha +
@ -223,9 +221,10 @@ class PiecesPainter extends PiecesBasePainter {
}
if (blurIndex != invalidIndex) {
final row = blurIndex! ~/ 7, column = blurIndex % 7;
final row = blurIndex! ~/ 7;
final column = blurIndex % 7;
paint.color = blurPositionColor!;
paint.color = blurPositionColor;
paint.style = PaintingStyle.fill;
canvas.drawCircle(

View File

@ -0,0 +1,81 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter_picker/flutter_picker.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
Future<int> showPickerNumber(
BuildContext context,
int begin,
int end,
int initValue,
String suffixString,
) async {
int selectValue = 0;
await Picker(
adapter: NumberPickerAdapter(
data: [
NumberPickerColumn(
begin: begin,
end: end,
initValue: initValue,
suffix: Text(
suffixString,
style: TextStyle(
fontSize: Config.fontSize,
),
),
),
],
),
hideHeader: true,
title: Text(
S.of(context).pleaseSelect,
style: TextStyle(
color: AppTheme.appPrimaryColor,
fontSize: Config.fontSize + 4.0,
),
),
textStyle: TextStyle(
color: Colors.black,
fontSize: Config.fontSize,
),
selectedTextStyle: const TextStyle(color: AppTheme.appPrimaryColor),
cancelText: S.of(context).cancel,
cancelTextStyle: TextStyle(
color: AppTheme.appPrimaryColor,
fontSize: Config.fontSize,
),
confirmText: S.of(context).confirm,
confirmTextStyle: TextStyle(
color: AppTheme.appPrimaryColor,
fontSize: Config.fontSize,
),
onConfirm: (Picker picker, List value) async {
debugPrint(value.toString());
final selectValues = picker.getSelectedValues();
debugPrint(selectValues.toString());
selectValue = selectValues[0];
},
).showDialog(context);
return selectValue;
}

View File

@ -17,24 +17,29 @@
*/
import 'package:flutter/material.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/shared/list_item_divider.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
class SettingsCard extends StatelessWidget {
const SettingsCard({
Key? key,
required this.context,
required this.children,
}) : super(key: key);
final BuildContext context;
final children;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Card(
color: AppTheme.cardColor,
margin: AppTheme.cardMargin,
child: Column(children: children),
child: ListView.separated(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (_, index) => children[index],
separatorBuilder: (_, __) => const ListItemDivider(),
itemCount: children.length,
),
);
}
}

View File

@ -18,14 +18,13 @@
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
class SettingsListTile extends StatelessWidget {
const SettingsListTile({
Key? key,
required this.context,
required this.titleString,
this.subtitleString,
this.trailingString,
@ -33,16 +32,16 @@ class SettingsListTile extends StatelessWidget {
required this.onTap,
}) : super(key: key);
final BuildContext context;
final String titleString;
final String? subtitleString;
final String? trailingString;
final int? trailingColor;
final onTap;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
bool ltr = getBidirectionality(context) == Bidirectionality.leftToRight;
final bool ltr =
getBidirectionality(context) == Bidirectionality.leftToRight;
return ListTile(
title: Text(
titleString,
@ -64,9 +63,7 @@ class SettingsListTile extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
trailingColor == null
? (trailingString == null ? "" : trailingString!)
: trailingColor!.toRadixString(16),
trailingColor?.toRadixString(16) ?? trailingString ?? '',
style: TextStyle(
fontSize: Config.fontSize,
backgroundColor:
@ -74,10 +71,11 @@ class SettingsListTile extends StatelessWidget {
),
),
Icon(
ltr
? FluentIcons.chevron_right_24_regular
: FluentIcons.chevron_left_24_regular,
color: AppTheme.listTileSubtitleColor)
ltr
? FluentIcons.chevron_right_24_regular
: FluentIcons.chevron_left_24_regular,
color: AppTheme.listTileSubtitleColor,
)
],
),
onTap: onTap,

View File

@ -17,24 +17,22 @@
*/
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/theme/app_theme.dart';
class SettingsSwitchListTile extends StatelessWidget {
const SettingsSwitchListTile({
Key? key,
required this.context,
required this.value,
required this.onChanged,
required this.titleString,
this.subtitleString,
}) : super(key: key);
final BuildContext context;
final value;
final bool value;
final String titleString;
final String? subtitleString;
final onChanged;
final ValueChanged<bool>? onChanged;
@override
Widget build(BuildContext context) {

View File

@ -17,21 +17,26 @@
*/
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/shared/common/config.dart';
void showSnackBar(BuildContext context, String message,
{Duration duration = const Duration(milliseconds: 4000)}) {
void showSnackBar(
BuildContext context,
String message, {
Duration duration = const Duration(milliseconds: 4000),
}) {
if (!Config.screenReaderSupport) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
}
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
message,
style: TextStyle(
fontSize: Config.fontSize,
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
message,
style: TextStyle(
fontSize: Config.fontSize,
),
),
duration: duration,
),
duration: duration,
));
);
}

View File

@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import 'package:sanmill/shared/common/config.dart';
import 'package:sanmill/shared/common/constants.dart';
import 'package:sanmill/shared/theme/colors.dart';
class AppTheme {
const AppTheme._();
// TODO: restructure theming. Some theme Elements should be accessed via Theme.of(context)
// Theme data
static final lightThemeData = ThemeData(
primarySwatch: AppTheme.appPrimaryColor,
brightness: Brightness.light,
);
static final darkThemeData = ThemeData(
primarySwatch: AppTheme.appPrimaryColor,
brightness: Brightness.dark,
);
// Color
static const appPrimaryColor = Colors.green; // Appbar & Dialog button
static const dialogTitleColor = appPrimaryColor;
/// Game page
static Color boardBackgroundColor = UIColors.burlyWood;
static Color mainToolbarBackgroundColor = UIColors.burlyWood;
static Color navigationToolbarBackgroundColor = UIColors.burlyWood;
static Color boardLineColor = const Color(0x996D000D);
static Color whitePieceColor = const Color.fromARGB(0xFF, 0xFF, 0xFF, 0xFF);
static Color whitePieceBorderColor =
const Color.fromARGB(0xFF, 0x66, 0x00, 0x00);
static Color blackPieceColor = const Color.fromARGB(0xFF, 0x00, 0x00, 0x00);
static Color blackPieceBorderColor =
const Color.fromARGB(0xFF, 0x22, 0x22, 0x22);
static Color pieceHighlightColor = Colors.red;
static Color messageColor = Colors.white;
static Color banColor =
const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00); // unused
static Color banBorderColor =
const Color.fromARGB(0x80, 0xFF, 0x00, 0x00); // unused
static Color mainToolbarIconColor = listTileSubtitleColor;
static Color navigationToolbarIconColor = listTileSubtitleColor;
static Color toolbarTextColor = mainToolbarIconColor;
static Color moveHistoryTextColor = Colors.yellow;
static Color moveHistoryDialogBackgroundColor = Colors.transparent;
static Color infoDialogBackgroundColor = moveHistoryDialogBackgroundColor;
static Color infoTextColor = moveHistoryTextColor;
static Color simpleDialogOptionTextColor = Colors.yellow;
/// Settings page
static Color darkBackgroundColor = UIColors.crusoe;
static Color lightBackgroundColor = UIColors.papayaWhip;
static Color listTileSubtitleColor = const Color(0x99461220);
static Color listItemDividerColor = const Color(0x336D000D);
static Color switchListTileActiveColor = dialogTitleColor;
static Color switchListTileTitleColor = UIColors.crusoe;
static Color cardColor = UIColors.floralWhite;
static Color settingsHeaderTextColor = UIColors.crusoe;
/// Help page
static Color helpBackgroundColor = boardBackgroundColor;
static Color helpTextColor = boardBackgroundColor;
/// About
static Color aboutPageBackgroundColor = lightBackgroundColor;
/// Drawer
static Color drawerColor = Colors.white;
static Color drawerBackgroundColor =
UIColors.notWhite.withOpacity(0.5); // TODO
static Color drawerHighlightItemColor =
UIColors.freeSpeechGreen.withOpacity(0.2);
static Color drawerDividerColor = UIColors.grey.withOpacity(0.6);
static Color drawerBoxerShadowColor = UIColors.grey.withOpacity(0.6);
static Color drawerTextColor = UIColors.nearlyBlack;
static Color drawerHighlightTextColor = UIColors.nearlyBlack;
static Color exitTextColor = UIColors.nearlyBlack;
static Color drawerIconColor = drawerTextColor;
static Color drawerHighlightIconColor = drawerHighlightTextColor;
static Color drawerAnimationIconColor = Colors.white;
static Color exitIconColor = Colors.red;
static Color drawerSplashColor =
Colors.grey.withOpacity(0.1); // TODO: no use?
static Color drawerHighlightColor = Colors.transparent; // TODO: no use?
static Color navigationHomeScreenBackgroundColor =
UIColors.nearlyWhite; // TODO: no use?
// Theme
static const sliderThemeData = SliderThemeData(
trackHeight: 20,
activeTrackColor: Colors.green,
inactiveTrackColor: Colors.grey,
disabledActiveTrackColor: Colors.yellow,
disabledInactiveTrackColor: Colors.cyan,
activeTickMarkColor: Colors.black,
inactiveTickMarkColor: Colors.green,
//overlayColor: Colors.yellow,
overlappingShapeStrokeColor: Colors.black,
//overlayShape: RoundSliderOverlayShape(),
valueIndicatorColor: Colors.green,
showValueIndicator: ShowValueIndicator.always,
minThumbSeparation: 100,
thumbShape: RoundSliderThumbShape(
enabledThumbRadius: 2.0,
disabledThumbRadius: 1.0,
),
rangeTrackShape: RoundedRectRangeSliderTrackShape(),
tickMarkShape: RoundSliderTickMarkShape(tickMarkRadius: 2.0),
valueIndicatorTextStyle: TextStyle(fontSize: 24),
);
static TextStyle simpleDialogOptionTextStyle = TextStyle(
fontSize: Config.fontSize + 4.0,
color: AppTheme.simpleDialogOptionTextColor,
);
static TextStyle moveHistoryTextStyle = TextStyle(
fontSize: Config.fontSize + 2.0,
height: 1.5,
color: moveHistoryTextColor,
);
static double boardTop = isLargeScreen ? 75.0 : 36.0;
static double boardMargin = 10.0;
static double boardScreenPaddingH = 10.0;
static double boardBorderRadius = 5.0;
static double boardPadding = 5.0;
static TextStyle settingsHeaderStyle =
TextStyle(color: settingsHeaderTextColor, fontSize: Config.fontSize + 4);
static TextStyle settingsTextStyle = TextStyle(fontSize: Config.fontSize);
static const cardMargin = EdgeInsets.symmetric(vertical: 4.0);
static const double drawerWidth = 250.0;
static const double sizedBoxHeight = 16.0;
static double copyrightFontSize = 12;
}

View File

@ -0,0 +1,46 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
class UIColors {
// https://www.htmlcsscolor.com
const UIColors._();
static const Color white = Color(0xFFFFFFFF);
static const Color notWhite = Color(0xFFEDF0F2);
static const Color nearlyWhite = Color(0xFFFEFEFE);
static const Color floralWhite = Color(0xFFFFFAF0);
static const Color nearlyBlack = Color(0xFF213333);
static const Color grey = Color(0xFF3A5160);
static const Color darkGrey = Color(0xFF313A44);
static const Color burlyWood = Color(0xFFDEB887);
static const Color cottonCandy = Color.fromARGB(0xFF, 255, 189, 219);
static const Color turkishRose = Color.fromARGB(0xFF, 163, 109, 173);
static const Color crusoe = Color(0xFF165B31);
static const Color forestGreen = Color(0xFF228B22);
static const Color freeSpeechGreen = Color(0xFF09F911);
static const Color oasis = Color.fromARGB(0xFF, 253, 239, 194);
static const Color papayaWhip = Color(0xFFFFEFD5);
static const Color stormGrey = Color.fromARGB(0xFF, 119, 121, 131);
static const Color turmeric = Color.fromARGB(0xFF, 186, 202, 68);
static const Color LavenderBlush = Color.fromARGB(0xFF, 255, 240, 246);
}

View File

@ -1,134 +0,0 @@
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/style/colors.dart';
class AppTheme {
AppTheme._();
// Theme data
static var lightThemeData = ThemeData(
primarySwatch: AppTheme.appPrimaryColor,
brightness: Brightness.light,
);
static var darkThemeData = ThemeData(
primarySwatch: AppTheme.appPrimaryColor,
brightness: Brightness.dark,
);
// Color
static var appPrimaryColor = Colors.green; // Appbar & Dialog button
static var dialogTitleColor = appPrimaryColor;
/// Game page
static var boardBackgroundColor = UIColors.burlyWood;
static var mainToolbarBackgroundColor = UIColors.burlyWood;
static var navigationToolbarBackgroundColor = UIColors.burlyWood;
static var boardLineColor = Color(0x996D000D);
static var whitePieceColor = Color.fromARGB(0xFF, 0xFF, 0xFF, 0xFF);
static var whitePieceBorderColor = Color.fromARGB(0xFF, 0x66, 0x00, 0x00);
static var blackPieceColor = Color.fromARGB(0xFF, 0x00, 0x00, 0x00);
static var blackPieceBorderColor = Color.fromARGB(0xFF, 0x22, 0x22, 0x22);
static var pieceHighlightColor = Colors.red;
static var messageColor = Colors.white;
static var banColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00); // unused
static var banBorderColor = Color.fromARGB(0x80, 0xFF, 0x00, 0x00); // unused
static var mainToolbarIconColor = listTileSubtitleColor;
static var navigationToolbarIconColor = listTileSubtitleColor;
static var toolbarTextColor = mainToolbarIconColor;
static var moveHistoryTextColor = Colors.yellow;
static var moveHistoryDialogBackgroundColor = Colors.transparent;
static var infoDialogBackgroundColor = moveHistoryDialogBackgroundColor;
static var infoTextColor = moveHistoryTextColor;
static var simpleDialogOptionTextColor = Colors.yellow;
/// Settings page
static var darkBackgroundColor = UIColors.crusoe;
static var lightBackgroundColor = UIColors.papayaWhip;
static var listTileSubtitleColor = Color(0x99461220);
static var listItemDividerColor = Color(0x336D000D);
static var switchListTileActiveColor = dialogTitleColor;
static var switchListTileTitleColor = UIColors.crusoe;
static const cardColor = UIColors.floralWhite;
static const settingsHeaderTextColor = UIColors.crusoe;
/// Help page
static var helpBackgroundColor = boardBackgroundColor;
static var helpTextColor = boardBackgroundColor;
/// About
static var aboutPageBackgroundColor = lightBackgroundColor;
/// Drawer
static var drawerColor = Colors.white;
static var drawerBackgroundColor = UIColors.notWhite.withOpacity(0.5); // TODO
static var drawerHighlightItemColor =
UIColors.freeSpeechGreen.withOpacity(0.2);
static var drawerDividerColor = UIColors.grey.withOpacity(0.6);
static var drawerBoxerShadowColor = UIColors.grey.withOpacity(0.6);
static var drawerTextColor = UIColors.nearlyBlack;
static var drawerHighlightTextColor = UIColors.nearlyBlack;
static var exitTextColor = UIColors.nearlyBlack;
static var drawerIconColor = drawerTextColor;
static var drawerHighlightIconColor = drawerHighlightTextColor;
static var drawerAnimationIconColor = Colors.white;
static var exitIconColor = Colors.red;
static var drawerSplashColor = Colors.grey.withOpacity(0.1); // TODO: no use?
static var drawerHighlightColor = Colors.transparent; // TODO: no use?
static var navigationHomeScreenBackgroundColor =
UIColors.nearlyWhite; // TODO: no use?
// Theme
static const sliderThemeData = SliderThemeData(
trackHeight: 20,
activeTrackColor: Colors.green,
inactiveTrackColor: Colors.grey,
disabledActiveTrackColor: Colors.yellow,
disabledInactiveTrackColor: Colors.cyan,
activeTickMarkColor: Colors.black,
inactiveTickMarkColor: Colors.green,
//overlayColor: Colors.yellow,
overlappingShapeStrokeColor: Colors.black,
//overlayShape: RoundSliderOverlayShape(),
valueIndicatorColor: Colors.green,
showValueIndicator: ShowValueIndicator.always,
minThumbSeparation: 100,
thumbShape: RoundSliderThumbShape(
enabledThumbRadius: 2.0, disabledThumbRadius: 1.0),
rangeTrackShape: RoundedRectRangeSliderTrackShape(),
tickMarkShape: RoundSliderTickMarkShape(tickMarkRadius: 2.0),
valueIndicatorTextStyle: TextStyle(fontSize: 24),
);
static var simpleDialogOptionTextStyle = TextStyle(
fontSize: Config.fontSize + 4.0,
color: AppTheme.simpleDialogOptionTextColor,
);
static var moveHistoryTextStyle = TextStyle(
fontSize: Config.fontSize + 2.0,
height: 1.5,
color: moveHistoryTextColor);
static double boardTop = isLargeScreen() ? 75.0 : 36.0;
static double boardMargin = 10.0;
static double boardScreenPaddingH = 10.0;
static double boardBorderRadius = 5.0;
static double boardPadding = 5.0;
static var settingsHeaderStyle =
TextStyle(color: settingsHeaderTextColor, fontSize: Config.fontSize + 4);
static var settingsTextStyle = TextStyle(fontSize: Config.fontSize);
static const cardMargin =
const EdgeInsets.symmetric(vertical: 4.0, horizontal: 0);
static const double drawerWidth = 250.0;
static const double sizedBoxHeight = 16.0;
static double copyrightFontSize = 12;
}

View File

@ -1,45 +0,0 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
class UIColors {
// https://www.htmlcsscolor.com
static const white = Color(0xFFFFFFFF);
static const notWhite = Color(0xFFEDF0F2);
static const nearlyWhite = Color(0xFFFEFEFE);
static const floralWhite = Color(0xFFFFFAF0);
static const nearlyBlack = Color(0xFF213333);
static const grey = Color(0xFF3A5160);
static const darkGrey = Color(0xFF313A44);
static const burlyWood = Color(0xFFDEB887);
static const cottonCandy = Color.fromARGB(0xFF, 255, 189, 219);
static const turkishRose = Color.fromARGB(0xFF, 163, 109, 173);
static const crusoe = Color(0xFF165B31);
static const forestGreen = Color(0xFF228B22);
static const freeSpeechGreen = Color(0xFF09F911);
static const oasis = Color.fromARGB(0xFF, 253, 239, 194);
static const papayaWhip = Color(0xFFFFEFD5);
static const stormGrey = Color.fromARGB(0xFF, 119, 121, 131);
static const turmeric = Color.fromARGB(0xFF, 186, 202, 68);
static const LavenderBlush = Color.fromARGB(0xFF, 255, 240, 246);
}

View File

@ -1,62 +0,0 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/generated/l10n.dart';
class EnvironmentVariablesPage extends StatefulWidget {
@override
_EnvironmentVariablesPageState createState() =>
_EnvironmentVariablesPageState();
}
class _EnvironmentVariablesPageState extends State<EnvironmentVariablesPage> {
String _data = "";
Future<void> _loadData() async {
final _loadedData =
await rootBundle.loadString(Constants.environmentVariablesFilename);
setState(() {
_data = _loadedData;
});
}
@override
Widget build(BuildContext context) {
_loadData();
return Scaffold(
appBar: AppBar(
title: Text(S.of(context).environmentVariables), centerTitle: true),
body: ListView(
children: <Widget>[
Container(
padding: const EdgeInsets.only(
top: 16, left: 16, right: 16, bottom: 16),
child: Text(
_data != "" ? _data : 'Nothing to show',
style: TextStyle(fontFamily: 'Monospace', fontSize: 12),
textAlign: TextAlign.left,
))
],
),
);
}
}

View File

@ -1,643 +0,0 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/common/settings.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/settings_card.dart';
import 'package:sanmill/widgets/settings_list_tile.dart';
import 'package:sanmill/widgets/settings_switch_list_tile.dart';
import 'dialog.dart';
import 'env_page.dart';
import 'list_item_divider.dart';
class Developer {
static bool developerModeEnabled = false;
}
class GameSettingsPage extends StatefulWidget {
@override
_GameSettingsPageState createState() => _GameSettingsPageState();
}
class _GameSettingsPageState extends State<GameSettingsPage> {
Color pickerColor = Color(0xFF808080);
Color currentColor = Color(0xFF808080);
late StreamController<int> _events;
var algorithmNames = ['Alpha-Beta', 'PVS', 'MTD(f)'];
final String tag = "[game_settings_page]";
@override
void initState() {
super.initState();
_events = StreamController<int>.broadcast();
_events.add(10);
}
void _restore() async {
final settings = await Settings.instance();
await settings.restore();
}
SliderTheme _skillLevelSliderTheme(context, setState) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).skillLevel,
child: Slider(
value: Config.skillLevel.toDouble(),
min: 1,
max: 30,
divisions: 29,
label: Config.skillLevel.round().toString(),
onChanged: (value) {
setState(() {
print("[config] Slider value: $value");
Config.skillLevel = value.toInt();
Config.save();
});
},
),
),
);
}
SliderTheme _moveTimeSliderTheme(context, setState) {
return SliderTheme(
data: AppTheme.sliderThemeData,
child: Semantics(
label: S.of(context).moveTime,
child: Slider(
value: Config.moveTime.toDouble(),
min: 0,
max: 60,
divisions: 60,
label: Config.moveTime.round().toString(),
onChanged: (value) {
setState(() {
print("[config] Slider value: $value");
Config.moveTime = value.toInt();
Config.save();
});
},
),
),
);
}
// Restore
restoreFactoryDefaultSettings() async {
confirm() async {
Navigator.of(context).pop();
if (Platform.isAndroid) {
showCountdownDialog(context, 10, _events, _restore);
} else {
_restore();
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(S.of(context).exitAppManually)));
}
}
cancel() => Navigator.of(context).pop();
var prompt = "";
if (Platform.isAndroid) {
prompt = S.of(context).exitApp;
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context).restore,
style: TextStyle(
color: AppTheme.dialogTitleColor,
fontSize: Config.fontSize + 4,
)),
content: SingleChildScrollView(
child: Text(
S.of(context).restoreDefaultSettings + "?\n" + prompt,
style: TextStyle(
fontSize: Config.fontSize,
),
),
),
actions: <Widget>[
TextButton(
child: Text(
S.of(context).ok,
style: TextStyle(
fontSize: Config.fontSize,
),
),
onPressed: confirm),
TextButton(
child: Text(
S.of(context).cancel,
style: TextStyle(
fontSize: Config.fontSize,
),
),
onPressed: cancel),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.lightBackgroundColor,
appBar: AppBar(
centerTitle: true,
title: Text(S.of(context).preferences),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children(context),
),
),
);
}
List<Widget> children(BuildContext context) {
return <Widget>[
Text(S.of(context).whoMovesFirst, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsSwitchListTile(
context: context,
value: !Config.aiMovesFirst,
onChanged: setWhoMovesFirst,
titleString:
Config.aiMovesFirst ? S.of(context).ai : S.of(context).human,
),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).difficulty, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsListTile(
context: context,
titleString: S.of(context).skillLevel,
//trailingString: "L" + Config.skillLevel.toString(),
onTap: setSkillLevel,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).moveTime,
onTap: setMoveTime,
),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).aisPlayStyle, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsListTile(
context: context,
titleString: S.of(context).algorithm,
trailingString: algorithmNames[Config.algorithm],
onTap: setAlgorithm,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.drawOnHumanExperience,
onChanged: setDrawOnHumanExperience,
titleString: S.of(context).drawOnHumanExperience,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.considerMobility,
onChanged: setConsiderMobility,
titleString: S.of(context).considerMobility,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.aiIsLazy,
onChanged: setAiIsLazy,
titleString: S.of(context).passive,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.shufflingEnabled,
onChanged: setShufflingEnabled,
titleString: S.of(context).shufflingEnabled,
),
],
),
!Platform.isWindows
? SizedBox(height: AppTheme.sizedBoxHeight)
: Container(height: 0.0, width: 0.0),
!Platform.isWindows
? Text(S.of(context).playSounds, style: AppTheme.settingsHeaderStyle)
: Container(height: 0.0, width: 0.0),
!Platform.isWindows
? SettingsCard(
context: context,
children: <Widget>[
SettingsSwitchListTile(
context: context,
value: Config.toneEnabled,
onChanged: setTone,
titleString: S.of(context).playSoundsInTheGame,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.keepMuteWhenTakingBack,
onChanged: setKeepMuteWhenTakingBack,
titleString: S.of(context).keepMuteWhenTakingBack,
),
],
)
: Container(height: 0.0, width: 0.0),
SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).accessibility, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsSwitchListTile(
context: context,
value: Config.screenReaderSupport,
onChanged: setScreenReaderSupport,
titleString: S.of(context).screenReaderSupport,
),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
Text(S.of(context).restore, style: AppTheme.settingsHeaderStyle),
SettingsCard(
context: context,
children: <Widget>[
SettingsListTile(
context: context,
titleString: S.of(context).restoreDefaultSettings,
onTap: restoreFactoryDefaultSettings,
),
ListItemDivider(),
],
),
SizedBox(height: AppTheme.sizedBoxHeight),
Developer.developerModeEnabled
? Text(S.of(context).forDevelopers,
style: AppTheme.settingsHeaderStyle)
: SizedBox(height: 1),
Developer.developerModeEnabled
? SettingsCard(
context: context,
children: <Widget>[
SettingsSwitchListTile(
context: context,
value: Config.developerMode,
onChanged: setDeveloperMode,
titleString: S.of(context).developerMode,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.experimentsEnabled,
onChanged: setExperimentsEnabled,
titleString: S.of(context).experiments,
),
ListItemDivider(),
SettingsSwitchListTile(
context: context,
value: Config.isAutoRestart,
onChanged: setIsAutoRestart,
titleString: S.of(context).isAutoRestart,
),
ListItemDivider(),
SettingsListTile(
context: context,
titleString: S.of(context).environmentVariables,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EnvironmentVariablesPage(),
),
);
},
),
],
)
: SizedBox(height: 1),
];
}
setSkillLevel() async {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return _skillLevelSliderTheme(context, setState);
},
),
);
}
setMoveTime() async {
showModalBottomSheet(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return _moveTimeSliderTheme(context, setState);
},
),
);
}
setWhoMovesFirst(bool value) async {
setState(() {
Config.aiMovesFirst = !value;
});
print("[config] aiMovesFirst: ${Config.aiMovesFirst}");
Config.save();
}
setAiIsLazy(bool value) async {
setState(() {
Config.aiIsLazy = value;
});
print("[config] aiMovesFirst: $value");
Config.save();
}
setAlgorithm() {
callback(int? algorithm) async {
print("[config] algorithm = $algorithm");
Navigator.of(context).pop();
setState(() {
Config.algorithm = algorithm ?? 2;
});
print("[config] Config.algorithm: ${Config.algorithm}");
Config.save();
}
showModalBottomSheet(
context: context,
builder: (BuildContext context) => Semantics(
label: S.of(context).algorithm,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('Alpha-Beta'),
groupValue: Config.algorithm,
value: 0,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('PVS'),
groupValue: Config.algorithm,
value: 1,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('MTD(f)'),
groupValue: Config.algorithm,
value: 2,
onChanged: callback,
),
ListItemDivider(),
],
),
),
);
}
setDrawOnHumanExperience(bool value) async {
setState(() {
Config.drawOnHumanExperience = value;
});
print("[config] drawOnHumanExperience: $value");
Config.save();
}
setConsiderMobility(bool value) async {
setState(() {
Config.considerMobility = value;
});
print("[config] considerMobility: $value");
Config.save();
}
setIsAutoRestart(bool value) async {
setState(() {
Config.isAutoRestart = value;
});
print("[config] isAutoRestart: $value");
Config.save();
}
setIsAutoChangeFirstMove(bool value) async {
setState(() {
Config.isAutoChangeFirstMove = value;
});
print("[config] isAutoChangeFirstMove: $value");
Config.save();
}
setResignIfMostLose(bool value) async {
setState(() {
Config.resignIfMostLose = value;
});
print("[config] resignIfMostLose: $value");
Config.save();
}
setShufflingEnabled(bool value) async {
setState(() {
Config.shufflingEnabled = value;
});
print("[config] shufflingEnabled: $value");
Config.save();
}
setLearnEndgame(bool value) async {
setState(() {
Config.learnEndgame = value;
});
print("[config] learnEndgame: $value");
Config.save();
}
setOpeningBook(bool value) async {
setState(() {
Config.openingBook = value;
});
print("[config] openingBook: $value");
Config.save();
}
setTone(bool value) async {
setState(() {
Config.toneEnabled = value;
});
print("[config] toneEnabled: $value");
Config.save();
}
setKeepMuteWhenTakingBack(bool value) async {
setState(() {
Config.keepMuteWhenTakingBack = value;
});
print("[config] keepMuteWhenTakingBack: $value");
Config.save();
}
setScreenReaderSupport(bool value) async {
setState(() {
Config.screenReaderSupport = value;
});
print("[config] screenReaderSupport: $value");
Config.save();
}
setDeveloperMode(bool value) async {
setState(() {
Config.developerMode = value;
});
print("[config] developerMode: $value");
Config.save();
}
setExperimentsEnabled(bool value) async {
setState(() {
Config.experimentsEnabled = value;
});
print("[config] experimentsEnabled: $value");
Config.save();
}
// Display
setLanguage(String value) async {
setState(() {
Config.languageCode = value;
});
print("[config] languageCode: $value");
Config.save();
}
setIsPieceCountInHandShown(bool value) async {
setState(() {
Config.isPieceCountInHandShown = value;
});
print("[config] isPieceCountInHandShown: $value");
Config.save();
}
setIsNotationsShown(bool value) async {
setState(() {
Config.isNotationsShown = value;
});
print("[config] isNotationsShown: $value");
Config.save();
}
setIsHistoryNavigationToolbarShown(bool value) async {
setState(() {
Config.isHistoryNavigationToolbarShown = value;
});
print("[config] isHistoryNavigationToolbarShown: $value");
Config.save();
}
setStandardNotationEnabled(bool value) async {
setState(() {
Config.standardNotationEnabled = value;
});
print("[config] standardNotationEnabled: $value");
Config.save();
}
}

View File

@ -1,58 +0,0 @@
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/style/colors.dart';
class HelpScreen extends StatefulWidget {
@override
_HelpScreenState createState() => _HelpScreenState();
}
class _HelpScreenState extends State<HelpScreen> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
color: UIColors.nearlyWhite,
child: SafeArea(
top: false,
child: Scaffold(
backgroundColor: Color(Config.darkBackgroundColor),
body: ListView(
children: <Widget>[
SizedBox(height: AppTheme.sizedBoxHeight),
Container(
padding: const EdgeInsets.only(
top: 48, left: 16, right: 16, bottom: 16),
child: Text(
S.of(context).howToPlay,
style: TextStyle(
fontSize: Config.fontSize + 4,
fontWeight: FontWeight.bold,
color: AppTheme.helpTextColor,
),
),
),
Container(
padding: const EdgeInsets.only(
top: 16, left: 16, right: 16, bottom: 16),
child: Text(
S.of(context).helpContent,
style: TextStyle(
fontSize: Config.fontSize,
color: AppTheme.helpTextColor,
),
),
),
],
),
),
),
);
}
}

View File

@ -1,363 +0,0 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/l10n/resources.dart';
import 'package:sanmill/style/app_theme.dart';
import 'package:sanmill/widgets/game_settings_page.dart';
enum DrawerIndex {
humanVsAi,
humanVsHuman,
aiVsAi,
preferences,
ruleSettings,
personalization,
feedback,
Help,
About
}
class DrawerListItem {
DrawerListItem({this.index, this.title = '', this.icon});
DrawerIndex? index;
String title;
Icon? icon;
}
class HomeDrawer extends StatefulWidget {
const HomeDrawer(
{Key? key,
this.screenIndex,
this.iconAnimationController,
this.callBackIndex})
: super(key: key);
final AnimationController? iconAnimationController;
final DrawerIndex? screenIndex;
final Function(DrawerIndex?)? callBackIndex;
@override
_HomeDrawerState createState() => _HomeDrawerState();
}
class _HomeDrawerState extends State<HomeDrawer> {
DateTime? lastTapTime;
final String tag = "[home_drawer]";
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
List<DrawerListItem> drawerList = <DrawerListItem>[
DrawerListItem(
index: DrawerIndex.humanVsAi,
title: S.of(context).humanVsAi,
icon: Icon(FluentIcons.person_24_regular),
),
DrawerListItem(
index: DrawerIndex.humanVsHuman,
title: S.of(context).humanVsHuman,
icon: Icon(FluentIcons.people_24_regular),
),
DrawerListItem(
index: DrawerIndex.aiVsAi,
title: S.of(context).aiVsAi,
icon: Icon(FluentIcons.bot_24_regular),
),
DrawerListItem(
index: DrawerIndex.preferences,
title: S.of(context).preferences,
icon: Icon(FluentIcons.options_24_regular),
),
DrawerListItem(
index: DrawerIndex.ruleSettings,
title: S.of(context).ruleSettings,
icon: Icon(FluentIcons.task_list_ltr_24_regular),
),
DrawerListItem(
index: DrawerIndex.personalization,
title: S.of(context).personalization,
icon: Icon(FluentIcons.design_ideas_24_regular),
),
DrawerListItem(
index: DrawerIndex.feedback,
title: S.of(context).feedback,
icon: Icon(FluentIcons.chat_warning_24_regular),
),
DrawerListItem(
index: DrawerIndex.Help,
title: S.of(context).help,
icon: Icon(FluentIcons.question_circle_24_regular),
),
DrawerListItem(
index: DrawerIndex.About,
title: S.of(context).about,
icon: Icon(FluentIcons.info_24_regular),
),
];
var rotationTransition = RotationTransition(
turns: AlwaysStoppedAnimation<double>(Tween<double>(begin: 0.0, end: 24.0)
.animate(CurvedAnimation(
parent: widget.iconAnimationController!,
curve: Curves.fastOutSlowIn))
.value /
360),
);
var scaleTransition = ScaleTransition(
scale: AlwaysStoppedAnimation<double>(
1.0 - (widget.iconAnimationController!.value) * 0.2),
child: rotationTransition,
);
var animatedBuilder = AnimatedBuilder(
animation: widget.iconAnimationController!,
builder: (BuildContext context, Widget? child) {
return scaleTransition;
},
);
var animatedTextsColors = [
Color(Config.drawerTextColor),
Colors.black,
Colors.blue,
Colors.yellow,
Colors.red,
Color(Config.darkBackgroundColor),
Color(Config.boardBackgroundColor),
Color(Config.drawerHighlightItemColor),
];
var animatedTextKit = AnimatedTextKit(
animatedTexts: [
ColorizeAnimatedText(
S.of(context).appName,
textStyle: TextStyle(
fontSize: Config.fontSize + 16,
fontWeight: FontWeight.w600,
),
colors: animatedTextsColors,
textAlign: TextAlign.start,
speed: const Duration(milliseconds: 3000),
),
],
pause: const Duration(milliseconds: 30000),
repeatForever: true,
stopPauseOnTap: true,
onTap: () {
if (lastTapTime == null ||
DateTime.now().difference(lastTapTime!) > Duration(seconds: 1)) {
lastTapTime = DateTime.now();
print("$tag Tap again in one second to enable developer mode.");
} else {
lastTapTime = DateTime.now();
Developer.developerModeEnabled = true;
print("$tag Developer mode enabled.");
}
});
var drawerHeader = Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 0.0),
child: Container(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
animatedBuilder,
Padding(
padding:
EdgeInsets.only(top: (isLargeScreen() ? 30 : 8), left: 4),
child: ExcludeSemantics(child: animatedTextKit),
),
],
),
),
);
/*
var exitListTile = ListTile(
title: Text(
S.of(context).exit,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: AppTheme.exitTextColor,
),
textAlign: TextAlign.left,
),
trailing: Icon(
FluentIcons.power_24_regular,
color: AppTheme.exitIconColor,
),
onTap: () async {
if (Config.developerMode) {
return;
}
await SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop');
},
);
*/
/*
var drawFooter = Column(
children: <Widget>[
exitListTile,
SizedBox(height: MediaQuery.of(context).padding.bottom)
],
);
*/
var scaffold = Scaffold(
backgroundColor: Color(Config.drawerBackgroundColor),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
drawerHeader,
const SizedBox(height: 4),
Divider(height: 1, color: AppTheme.drawerDividerColor),
Expanded(
child: ListView.builder(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(0.0),
itemCount: drawerList.length,
itemBuilder: (BuildContext context, int index) {
return buildInkwell(drawerList[index]);
},
),
),
Divider(height: 1, color: AppTheme.drawerDividerColor),
//drawFooter,
],
),
);
return scaffold;
}
Future<void> navigationToScreen(DrawerIndex? index) async {
widget.callBackIndex!(index);
}
Widget buildInkwell(DrawerListItem listItem) {
bool ltr = getBidirectionality(context) == Bidirectionality.leftToRight;
double radius = 28.0;
var animatedBuilder = AnimatedBuilder(
animation: widget.iconAnimationController!,
builder: (BuildContext context, Widget? child) {
return Transform(
transform: Matrix4.translationValues(
(MediaQuery.of(context).size.width * 0.75 - 64) *
(1.0 - widget.iconAnimationController!.value - 1.0),
0.0,
0.0),
child: Padding(
padding: EdgeInsets.only(top: 8, bottom: 8),
child: Container(
width: MediaQuery.of(context).size.width * 0.75 - 64,
height: 46,
decoration: BoxDecoration(
color: Color(Config.drawerHighlightItemColor),
borderRadius: new BorderRadius.only(
topLeft: Radius.circular(ltr ? 0 : radius),
topRight: Radius.circular(ltr ? radius : 0),
bottomLeft: Radius.circular(ltr ? 0 : radius),
bottomRight: Radius.circular(ltr ? radius : 0),
),
),
),
),
);
},
);
var listItemIcon = Icon(listItem.icon!.icon,
color: widget.screenIndex == listItem.index
? Color(Config.drawerTextColor) // TODO: drawerHighlightTextColor
: Color(Config.drawerTextColor));
var stack = Stack(
children: <Widget>[
Container(
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Row(
children: <Widget>[
Container(
width: 6.0,
height: 46.0,
),
const Padding(
padding: EdgeInsets.all(4.0),
),
listItemIcon,
const Padding(
padding: EdgeInsets.all(4.0),
),
Text(
listItem.title,
style: TextStyle(
fontWeight: widget.screenIndex == listItem.index
? FontWeight.w700
: FontWeight.w500,
fontSize: Config.fontSize,
color: widget.screenIndex == listItem.index
? Color(Config
.drawerTextColor) // TODO: drawerHighlightTextColor
: Color(Config.drawerTextColor),
),
),
],
),
),
widget.screenIndex == listItem.index
? animatedBuilder
: const SizedBox()
],
);
return Material(
// Semantics: Main menu item
color: Colors.transparent,
child: InkWell(
splashColor: AppTheme.drawerSplashColor,
highlightColor: AppTheme.drawerHighlightColor,
onTap: () {
navigationToScreen(listItem.index);
},
child: stack,
),
);
}
}

View File

@ -1,60 +0,0 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:sanmill/common/constants.dart';
import 'package:sanmill/generated/l10n.dart';
class LicenseAgreementPage extends StatefulWidget {
@override
_LicenseAgreementPageState createState() => _LicenseAgreementPageState();
}
class _LicenseAgreementPageState extends State<LicenseAgreementPage> {
String _data = "";
Future<void> _loadData() async {
final _loadedData =
await rootBundle.loadString(Constants.gplLicenseFilename);
setState(() {
_data = _loadedData;
});
}
@override
Widget build(BuildContext context) {
_loadData();
return Scaffold(
appBar: AppBar(title: Text(S.of(context).license), centerTitle: true),
body: ListView(
children: <Widget>[
Container(
padding: const EdgeInsets.only(
top: 16, left: 16, right: 16, bottom: 16),
child: Text(
_data != "" ? _data : 'Nothing to show',
style: TextStyle(fontFamily: 'Monospace', fontSize: 12),
textAlign: TextAlign.left,
))
],
),
);
}
}

View File

@ -1,145 +0,0 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/generated/oss_licenses.dart';
import 'package:url_launcher/url_launcher.dart';
class FlutterLicense extends LicenseEntry {
final List<String> packages;
final List<LicenseParagraph> paragraphs;
FlutterLicense(this.packages, this.paragraphs);
}
/// display all used packages and their license
class OssLicensesPage extends StatelessWidget {
static Future<List<String>> loadLicenses() async {
Stream<LicenseEntry> licenses() async* {
yield FlutterLicense([
'Sound Effects'
], [
LicenseParagraph(
'CC-0\nhttps://freesound.org/people/unfa/sounds/243749/', 0)
]);
}
LicenseRegistry.addLicense(licenses);
// merging non-dart based dependency list using LicenseRegistry.
final ossKeys = ossLicenses.keys.toList();
final lm = <String, List<String>>{};
await for (var l in LicenseRegistry.licenses) {
for (var p in l.packages) {
if (!ossKeys.contains(p)) {
final lp = lm.putIfAbsent(p, () => []);
lp.addAll(l.paragraphs.map((p) => p.text));
ossKeys.add(p);
}
}
}
for (var key in lm.keys) {
ossLicenses[key] = {'license': lm[key]!.join('\n')};
}
return ossKeys..sort();
}
static final _licenses = loadLicenses();
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(S.of(context).ossLicenses),
),
body: FutureBuilder<List<String>>(
future: _licenses,
builder: (context, snapshot) => ListView.separated(
padding: const EdgeInsets.all(0),
itemCount: snapshot.data?.length ?? 0,
itemBuilder: (context, index) {
final key = snapshot.data![index];
final ossl = ossLicenses[key];
final version = ossl['version'];
final desc = ossl['description'];
return ListTile(
title: Text('$key ${version ?? ''}'),
subtitle: desc != null ? Text(desc) : null,
trailing: Icon(FluentIcons.chevron_right_24_regular),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
MiscOssLicenseSingle(name: key, json: ossl))));
},
separatorBuilder: (context, index) => const Divider())));
}
class MiscOssLicenseSingle extends StatelessWidget {
final String name;
final Map<String, dynamic> json;
String get version => json['version'] == null ? "" : json['version'];
String? get description => json['description'];
String get licenseText => json['license'];
String? get homepage => json['homepage'];
MiscOssLicenseSingle({required this.name, required this.json});
String _bodyText() => licenseText.split('\n').map((line) {
if (line.startsWith('//')) line = line.substring(2);
line = line.trim();
return line;
}).join('\n');
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text('$name $version')),
body: Container(
color: Theme.of(context).canvasColor,
child: ListView(children: <Widget>[
if (description != null)
Padding(
padding: const EdgeInsets.only(
top: 12.0, left: 12.0, right: 12.0),
child: Text(description!,
style: Theme.of(context)
.textTheme
.bodyText2!
.copyWith(fontWeight: FontWeight.bold))),
if (homepage != null)
Padding(
padding: const EdgeInsets.only(
top: 12.0, left: 12.0, right: 12.0),
child: InkWell(
child: Text(homepage!,
style: const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline)),
onTap: () => launch(homepage!),
)),
if (description != null || homepage != null) const Divider(),
Padding(
padding:
const EdgeInsets.only(top: 12.0, left: 12.0, right: 12.0),
child: Text(_bodyText(),
style: Theme.of(context).textTheme.bodyText2),
),
])),
);
}

View File

@ -1,73 +0,0 @@
/*
This file is part of Sanmill.
Copyright (C) 2019-2021 The Sanmill developers (see AUTHORS file)
Sanmill is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sanmill is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter_picker/flutter_picker.dart';
import 'package:sanmill/common/config.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/style/app_theme.dart';
Future<int> showPickerNumber(BuildContext context, int begin, int end,
int initValue, String suffixString) async {
int selectValue = 0;
await Picker(
adapter: NumberPickerAdapter(
data: [
NumberPickerColumn(
begin: begin,
end: end,
initValue: initValue,
suffix: Text(
suffixString,
style: TextStyle(
fontSize: Config.fontSize,
),
),
),
],
),
hideHeader: true,
title: Text(S.of(context).pleaseSelect,
style: TextStyle(
color: AppTheme.appPrimaryColor,
fontSize: Config.fontSize + 4.0,
)),
textStyle: TextStyle(
color: Colors.black,
fontSize: Config.fontSize,
),
selectedTextStyle: TextStyle(color: AppTheme.appPrimaryColor),
cancelText: S.of(context).cancel,
cancelTextStyle: TextStyle(
color: AppTheme.appPrimaryColor,
fontSize: Config.fontSize,
),
confirmText: S.of(context).confirm,
confirmTextStyle: TextStyle(
color: AppTheme.appPrimaryColor,
fontSize: Config.fontSize,
),
onConfirm: (Picker picker, List value) async {
print(value.toString());
var selectValues = picker.getSelectedValues();
print(selectValues);
selectValue = selectValues[0];
}).showDialog(context);
return selectValue;
}

View File

@ -1,37 +1,37 @@
name: sanmill
description: Sanmill is a open-source, powerful UCI-like Nine Men's Morris (and its variants) program.
publish_to: none
version: 1.1.38+2196
environment:
sdk: '>=2.12.0 <3.0.0'
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
cupertino_icons: ^1.0.3
soundpool: ^2.2.0
path_provider: ^2.0.5
package_info_plus: ^1.0.6
uuid: ^3.0.4
url_launcher: ^6.0.11
intl: 0.17.0
animated_text_kit: ^4.1.1
catcher: ^0.6.8
stack_trace: ^1.10.0
cupertino_icons: ^1.0.3
device_info_plus_platform_interface: ^2.1.0
devicelocale: ^0.4.3
double_back_to_close_app: ^2.0.1
flutter_picker: ^2.0.2
flutter_email_sender: ^5.0.2
feedback:
git:
url: git://github.com/calcitem/feedback.git
animated_text_kit: ^4.1.1
flutter_colorpicker: ^0.6.0
device_info_plus_platform_interface: ^2.1.0
fluentui_system_icons: ^1.1.140
flutter:
sdk: flutter
flutter_colorpicker: ^0.6.0
flutter_email_sender: ^5.0.2
flutter_localizations:
sdk: flutter
flutter_picker: ^2.0.2
intl: 0.17.0
package_info_plus: ^1.0.6
path_provider: ^2.0.5
soundpool: ^2.2.0
stack_trace: ^1.10.0
url_launcher: ^6.0.11
uuid: ^3.0.4
#pref: ^2.3.0
#screen_recorder: ^0.0.2
@ -39,6 +39,7 @@ dev_dependencies:
flutter_oss_licenses: ^1.0.1
flutter_test:
sdk: flutter
lint: ^1.7.2
msix: ^2.1.3
flutter:
@ -67,5 +68,5 @@ msix_config:
publisher_display_name: Calcitem Studio
identity_name: 25314CalcitemStudio.Sanmill
publisher: CN=3413C020-B420-4E0A-8687-A2C35E878F3A
capabilities: ''
capabilities: ""
store: true

View File

@ -20,7 +20,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sanmill/generated/l10n.dart';
import 'package:sanmill/widgets/navigation_home_screen.dart';
import 'package:sanmill/screens/navigation_home_screen.dart';
void main() {
Widget makeTestableWidget({required Widget child, required Locale locale}) {
@ -38,11 +38,13 @@ void main() {
}
testWidgets('Widget', (WidgetTester tester) async {
NavigationHomeScreen screen = NavigationHomeScreen();
await tester.pumpWidget(makeTestableWidget(
child: screen,
locale: const Locale('en'),
));
final NavigationHomeScreen screen = NavigationHomeScreen();
await tester.pumpWidget(
makeTestableWidget(
child: screen,
locale: const Locale('en'),
),
);
await tester.pump();
expect(find.text(S.current.appName), findsOneWidget);
});