From 186870e770eaa93c874f04ca38bc929daafb9c23 Mon Sep 17 00:00:00 2001 From: DMK Date: Sun, 1 Aug 2021 21:13:25 +0300 Subject: [PATCH] Split singe file into set of src files --- bin/dartsunfish.dart | 2 +- lib/index.dart | 5 + lib/src/dartsunfish.dart | 87 +++++ lib/src/extentions.dart | 16 + lib/{dartsunfish.dart => src/game_play.dart} | 316 +------------------ lib/src/globals.dart | 145 +++++++++ lib/src/ui.dart | 47 +++ 7 files changed, 312 insertions(+), 306 deletions(-) create mode 100644 lib/index.dart create mode 100644 lib/src/dartsunfish.dart create mode 100644 lib/src/extentions.dart rename lib/{dartsunfish.dart => src/game_play.dart} (56%) create mode 100644 lib/src/globals.dart create mode 100644 lib/src/ui.dart diff --git a/bin/dartsunfish.dart b/bin/dartsunfish.dart index a6d7a8d..0f45ab3 100644 --- a/bin/dartsunfish.dart +++ b/bin/dartsunfish.dart @@ -1,4 +1,4 @@ -import 'package:dartsunfish/dartsunfish.dart' as dartsunfish; +import 'package:dartsunfish/index.dart' as dartsunfish; void main(List arguments) { dartsunfish.GameBot().playGame(); diff --git a/lib/index.dart b/lib/index.dart new file mode 100644 index 0000000..887b039 --- /dev/null +++ b/lib/index.dart @@ -0,0 +1,5 @@ +export 'src/dartsunfish.dart'; +export 'src/extentions.dart'; +export 'src/globals.dart'; +export 'src/game_play.dart'; +export 'src/ui.dart'; diff --git a/lib/src/dartsunfish.dart b/lib/src/dartsunfish.dart new file mode 100644 index 0000000..6125914 --- /dev/null +++ b/lib/src/dartsunfish.dart @@ -0,0 +1,87 @@ +import 'dart:io'; +import 'dart:developer' as dv; +import 'package:dartsunfish/index.dart'; + +class GameBot { + GameBot(); + + void gameMain() { + var hist = [ + Position(initial, 0, [true, true], [true, true], 0, 0), + ]; + var searcher = Searcher(); + while (true) { + UI.printBoard(hist.last); + + if (hist.last.score <= -MATE_LOWER) { + print('You lost'); + break; + } + + // We query the user until she enters a (pseudo) legal move. + var move = []; + final possibleMoves = hist.last.generateMoves().toList(); + while (!possibleMoves.doContains(move)) { + var pattern = RegExp( + r'([a-h][1-8])' * 2, + caseSensitive: false, + multiLine: false, + ); + print('Your move:'); + var input = stdin.readLineSync(); + var match = pattern.firstMatch(input.toString()); + if (match != null) { + move = [UI.parse(match.group(1)), UI.parse(match.group(2))]; + if (!possibleMoves.doContains(move)) { + print('Not valid move typed in. Try again'); + } + } else { + // Inform the user when invalid input (e.g. "help") is entered + print('Please enter a move like g8f6'); + } + } + try { + hist.add(hist.last.movePiece(move)); + } catch (e) { + // print(e.toString()); + } + + // After our move we rotate the board and print it again. + // This allows us to see the effect of our move. + UI.printBoard(hist.last.rotate()); + // print('${hist.last.score}'); + + if (hist.last.score <= -MATE_LOWER) { + print('You won'); + break; + } + // Fire up the engine to look for a move. + var _depth = 0; + var score = 0; + var start = getSeconds(); +/* + */ + for (var s in searcher.search(hist.last, hist.toSet())) { + _depth = s[0]; + move = s[1]; + score = s[2] ?? 0; + if (getSeconds() - start > 1) { + break; + } + } + + if (score == MATE_UPPER) { + print('Checkmate!'); + } + // The black player moves from a rotated position, so we have to + // 'back rotate' the move before printing it. + print('My move: ${UI.render(119 - move[0]) + UI.render(119 - move[1])}'); + hist.add(hist.last.movePiece(move)); + } + } + + void playGame() { + setPst(); + gameMain(); + } +} diff --git a/lib/src/extentions.dart b/lib/src/extentions.dart new file mode 100644 index 0000000..f1731da --- /dev/null +++ b/lib/src/extentions.dart @@ -0,0 +1,16 @@ +///String extentions +extension CheckString on String { + bool get isSpace => trim().isEmpty || RegExp(r'/\s/').hasMatch(this); + bool get isUpper => !isSpace && toUpperCase() == this && this != '.'; + bool get isLower => !isSpace && toLowerCase() == this && this != '.'; + String get reversed => split('').reversed.join(''); + String get swapCase => + split('').map((x) => x.toUpperCase() == x ? x.toLowerCase() : x.toUpperCase()).toList().join(''); + String get reversedAndSwapCased => + split('').map((x) => x.toUpperCase() == x ? x.toLowerCase() : x.toUpperCase()).toList().reversed.join(''); +} + +// List of list extention +extension CheckListOfTuples on List { + bool doContains(newList) => newList.length == 2 && indexWhere((l) => l[0] == newList[0] && l[1] == newList[1]) > -1; +} diff --git a/lib/dartsunfish.dart b/lib/src/game_play.dart similarity index 56% rename from lib/dartsunfish.dart rename to lib/src/game_play.dart index 41a7b89..eb0b009 100644 --- a/lib/dartsunfish.dart +++ b/lib/src/game_play.dart @@ -1,169 +1,6 @@ -import 'dart:io'; import 'dart:math'; -import 'dart:developer' as dv; -///String extentions -extension CheckString on String { - bool get isSpace => trim().isEmpty || RegExp(r'/\s/').hasMatch(this); - bool get isUpper => !isSpace && toUpperCase() == this && this != '.'; - bool get isLower => !isSpace && toLowerCase() == this && this != '.'; - String get reversed => split('').reversed.join(''); - String get swapCase => - split('').map((x) => x.toUpperCase() == x ? x.toLowerCase() : x.toUpperCase()).toList().join(''); - String get reversedAndSwapCased => - split('').map((x) => x.toUpperCase() == x ? x.toLowerCase() : x.toUpperCase()).toList().reversed.join(''); -} - -// List of list extention -extension CheckListOfTuples on List { - bool doContains(newList) => newList.length == 2 && indexWhere((l) => l[0] == newList[0] && l[1] == newList[1]) > -1; -} - -int getSeconds() { - return DateTime.now().millisecondsSinceEpoch ~/ 1000.toInt(); -} - -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// // Piece-Square tables. Tune these to change sunfish's behaviour -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// TODO: add wizard -final piece = {'P': 100, 'N': 280, 'B': 320, 'R': 479, 'Q': 929, 'K': 60000}; - -///TODO: add 2 raws and 2 colums to each set AND Wizard pst -var pst = { - 'P': [ - 0, 0, 0, 0, 0, 0, 0, 0, // - 78, 83, 86, 73, 102, 82, 85, 90, // - 7, 29, 21, 44, 40, 31, 44, 7, // - -17, 16, -2, 15, 14, 0, 15, -13, // - -26, 3, 10, 9, 6, 1, 0, -23, // - -22, 9, 5, -11, -10, -2, 3, -19, // - -31, 8, -7, -37, -36, -14, 3, -31, // - 0, 0, 0, 0, 0, 0, 0, 0 - ], // - 'N': [ - -66, -53, -75, -75, -10, -55, -58, -70, // - -3, -6, 100, -36, 4, 62, -4, -14, // - 10, 67, 1, 74, 73, 27, 62, -2, // - 24, 24, 45, 37, 33, 41, 25, 17, // - -1, 5, 31, 21, 22, 35, 2, 0, // - -18, 10, 13, 22, 18, 15, 11, -14, // - -23, -15, 2, 0, 2, 0, -23, -20, // - -74, -23, -26, -24, -19, -35, -22, -69 - ], // - 'B': [ - -59, -78, -82, -76, -23, -107, -37, -50, // - -11, 20, 35, -42, -39, 31, 2, -22, // - -9, 39, -32, 41, 52, -10, 28, -14, // - 25, 17, 20, 34, 26, 25, 15, 10, // - 13, 10, 17, 23, 17, 16, 0, 7, // - 14, 25, 24, 15, 8, 25, 20, 15, // - 19, 20, 11, 6, 7, 6, 20, 16, // - -7, 2, -15, -12, -14, -15, -10, -10 - ], // - 'R': [ - 35, 29, 33, 4, 37, 33, 56, 50, // - 55, 29, 56, 67, 55, 62, 34, 60, // - 19, 35, 28, 33, 45, 27, 25, 15, // - 0, 5, 16, 13, 18, -4, -9, -6, // - -28, -35, -16, -21, -13, -29, -46, -30, // - -42, -28, -42, -25, -25, -35, -26, -46, // - -53, -38, -31, -26, -29, -43, -44, -53, // - -30, -24, -18, 5, -2, -18, -31, -32 - ], // - 'Q': [ - 6, 1, -8, -104, 69, 24, 88, 26, // - 14, 32, 60, -10, 20, 76, 57, 24, // - -2, 43, 32, 60, 72, 63, 43, 2, // - 1, -16, 22, 17, 25, 20, -13, -6, // - -14, -15, -2, -5, -1, -10, -20, -22, // - -30, -6, -13, -11, -16, -11, -16, -27, // - -36, -18, 0, -19, -15, -15, -21, -38, // - -39, -30, -31, -13, -31, -36, -34, -42 - ], // - 'K': [ - 4, 54, 47, -99, -99, 60, 83, -62, // - -32, 10, 55, 56, 56, 55, 10, 3, // - -62, 12, -57, 44, -67, 28, 37, -31, // - -55, 50, 11, -4, -19, 13, 0, -49, // - -55, -43, -52, -28, -51, -47, -8, -50, // - -47, -42, -43, -79, -64, -32, -29, -32, // - -4, 3, -14, -50, -57, -18, 13, 4, // - 17, 30, -3, -14, 6, -1, 40, 18 - ], // -}; - -// recalculating Piece-Square raw into desk surrounded by 0-s -List padrow(List row, String k) { - var rowBody = [for (int x in row) x + piece[k]!]; - return [ - 0, - ...rowBody, - 0, - ]; -} - -void setPst() { - pst.forEach((key, item) { - final innerItem = [...List.filled(20, 0)]; - for (var i = 0; i < 8; i++) { - innerItem.addAll(padrow(item.getRange(i * 8, i * 8 + 8).toList(), key)); - } - innerItem.addAll(List.filled(20, 0)); - pst[key] = innerItem; - }); -} - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Global constants -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Our board is represented as a 120 character string. The padding allows for -// fast detection of moves that don't stay within the board. - -//TODO: Tune globals for 10x10 field -final A1 = 91, H1 = 98, A8 = 21, H8 = 28; -final initial = (' \n' // 0 - 9 - ' \n' // 10 - 19 - ' rnbqkbnr\n' // 20 - 29 - ' pppppppp\n' // 30 - 39 - ' ........\n' // 40 - 49 - ' ........\n' // 50 - 59 - ' ........\n' // 60 - 69 - ' ........\n' // 70 - 79 - ' PPPPPPPP\n' // 80 - 89 - ' RNBQKBNR\n' // 90 - 99 - ' \n' // 100 -109 - ' \n' // 110 -119 - ); - -// Lists of possible moves for each piece type. -int N = -10, E = 1, S = 10, W = -1; -//TODO: add directions for Wizard -final Map> directions = { - 'P': [N, N + N, N + W, N + E], - 'N': [N + N + E, E + N + E, E + S + E, S + S + E, S + S + W, W + S + W, W + N + W, N + N + W], - 'B': [N + E, S + E, S + W, N + W], - 'R': [N, E, S, W], - 'Q': [N, E, S, W, N + E, S + E, S + W, N + W], - 'K': [N, E, S, W, N + E, S + E, S + W, N + W], - '.': [] -}; - -// Mate value must be greater than 8*queen + 2*(rook+knight+bishop) -// King value is set to twice this value such that if the opponent is -// 8 queens up, but we got the king, we still exceed MATE_VALUE. -// When a MATE is detected, we'll set the score to MATE_UPPER - plies to get there -// E.g. Mate in 3 will be MATE_UPPER - 6 -// TOD: replace mate with King capture event -final MATE_LOWER = piece['K']! - 10 * piece['Q']!; -final MATE_UPPER = piece['K']! + 10 * piece['Q']!; - -// The table size is the maximum number of elements in the transposition table. -final TABLE_SIZE = 1e7; - -// Constants for tuning search -final QS_LIMIT = 219, EVAL_ROUGHNESS = 13, DRAW_TEST = true; +import 'package:dartsunfish/index.dart'; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Chess logic @@ -194,7 +31,7 @@ class Position { kp - the king passant square """; */ - Iterable> gen_moves() sync* { + Iterable> generateMoves() sync* { // For each of our pieces, iterate through each possible 'ray' of moves, // as defined in the 'directions' map. The rays are broken e.g. by // captures or immediately in case of pieces such as knights. @@ -391,13 +228,13 @@ class Searcher { late Map tpScore = {}; late Map?> tpMove = {}; late Set history; - int nodes = 0; + // int nodes = 0; int bound(Position pos, int gamma, int depth, {bool root = true}) { ''' returns r where s(pos) <= r < gamma if gamma > s(pos) gamma <= r <= s(pos) if gamma <= s(pos)'''; - nodes += 1; + // nodes += 1; // Depth <= 0 is QSearch. Here any position is searched as deeply as is needed for // calmness, and from this point on there is no difference in behaviour depending on @@ -465,12 +302,12 @@ class Searcher { yield ([killer, -1 * bound(pos.movePiece(killer), 1 - gamma, depth - 1, root: false)]); } // Then all the other moves - var possibleMoves = pos.gen_moves().toList() + var possibleMoves = pos.generateMoves().toList() ..sort((a, b) => pos.value(a).compareTo(pos.value(b))) ..toList(); //.reversed; for (var move in possibleMoves.reversed) { - //pos.gen_moves().toList().sort((a, b) => a.value.compareto(b.value)).toList().reversed)) { - // for val, move in sorted(((pos.value(move), move) for move in pos.gen_moves()), reverse=true): + //pos.generateMoves().toList().sort((a, b) => a.value.compareto(b.value)).toList().reversed)) { + // for val, move in sorted(((pos.value(move), move) for move in pos.generateMoves()), reverse=true): // If depth == 0 we only try moves with high intrinsic score (captures and // promotions). Otherwise we do all moves. if (depth > 0 || pos.value(move) >= QS_LIMIT) { @@ -525,8 +362,8 @@ class Searcher { return pos.value.any((m) => m >= MATE_LOWER); } - // if (all(is_dead(pos.move(m)) for m in pos.gen_moves())){ - if (pos.gen_moves().every((m) => is_dead(pos.movePiece(m)))) { + // if (all(is_dead(pos.move(m)) for m in pos.generateMoves())){ + if (pos.generateMoves().every((m) => is_dead(pos.movePiece(m)))) { var in_check = is_dead(pos.nullmove()); best = in_check ? -MATE_UPPER : 0; } @@ -547,11 +384,11 @@ class Searcher { Iterable search(Position pos, Set _history) sync* { ''' Iterative deepening MTD-bi search '''; - nodes = 0; + // nodes = 0; if (DRAW_TEST) { history = _history; - // print('// Clearing table due to new history') + // Clearing table due to new history tpScore.clear(); } // In finished games, we could potentially go far enough to cause a recursion @@ -582,134 +419,3 @@ class Searcher { } } } - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// User interface -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -class UI { - static int parse(String? c) { - var fil = c![0].codeUnits.first - 'a'.codeUnits.first; - var rank = int.parse(c[1]) - 1; - return A1 + fil - 10 * rank; - } - - static String render(int i) { - var rank = (i - A1) ~/ 10; - var fil = (i - A1) % 10; - return String.fromCharCodes([fil + 'a'.codeUnits.first]) + (-rank + 1).toString(); - } - - static void printBoard(Position pos) { - final uni_pieces = { - 'R': '♜', - 'N': '♞', - 'B': '♝', - 'Q': '♛', - 'K': '♚', - 'P': '♟', - 'r': '♖', - 'n': '♘', - 'b': '♗', - 'q': '♕', - 'k': '♔', - 'p': '♙', - '.': '·', - // ' ': ' ' - }; - print(''); - final bordChars = pos.board.split('\n'); - for (var i = 0; i < bordChars.length; i++) { - var row = bordChars[i]; - if (!row.isSpace) { - var pieces = row.split('').map((e) => e = e.isSpace ? '' : uni_pieces[e]!); - print('${(10 - i)}${pieces.join(' ')}'); - } - } - print(' a b c d e f g h \n\n'); - } -} - -class GameBot { - GameBot(); - - void gameMain() { - var hist = [ - Position(initial, 0, [true, true], [true, true], 0, 0), - ]; - var searcher = Searcher(); - while (true) { - UI.printBoard(hist.last); - - if (hist.last.score <= -MATE_LOWER) { - print('You lost'); - break; - } - - // We query the user until she enters a (pseudo) legal move. - var move = []; - final possibleMoves = hist.last.gen_moves().toList(); - while (!possibleMoves.doContains(move)) { - var pattern = RegExp( - r'([a-h][1-8])' * 2, - caseSensitive: false, - multiLine: false, - ); - print('Your move:'); - var input = stdin.readLineSync(); - var match = pattern.firstMatch(input.toString()); - if (match != null) { - move = [UI.parse(match.group(1)), UI.parse(match.group(2))]; - if (!possibleMoves.doContains(move)) { - print('Not valid move typed in. Try again'); - } - } else { - // Inform the user when invalid input (e.g. "help") is entered - print('Please enter a move like g8f6'); - } - } - try { - hist.add(hist.last.movePiece(move)); - } catch (e) { - // print(e.toString()); - } - - // After our move we rotate the board and print it again. - // This allows us to see the effect of our move. - UI.printBoard(hist.last.rotate()); - // print('${hist.last.score}'); - - if (hist.last.score <= -MATE_LOWER) { - print('You won'); - break; - } - // Fire up the engine to look for a move. - var _depth = 0; - var score = 0; - var start = getSeconds(); -/* - */ - for (var s in searcher.search(hist.last, hist.toSet())) { - _depth = s[0]; - move = s[1]; - score = s[2] ?? 0; - if (getSeconds() - start > 1) { - break; - } - } - - if (score == MATE_UPPER) { - print('Checkmate!'); - } - // The black player moves from a rotated position, so we have to - // 'back rotate' the move before printing it. - print('My move: ${UI.render(119 - move[0]) + UI.render(119 - move[1])}'); - hist.add(hist.last.movePiece(move)); - } - } - - void playGame() { - dv.log('message'); - setPst(); - gameMain(); - } -} diff --git a/lib/src/globals.dart b/lib/src/globals.dart new file mode 100644 index 0000000..677be9f --- /dev/null +++ b/lib/src/globals.dart @@ -0,0 +1,145 @@ +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// // Piece-Square tables. Tune these to change sunfish's behaviour +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// TODO: add wizard +final piece = {'P': 100, 'N': 280, 'B': 320, 'R': 479, 'Q': 929, 'K': 60000}; + +///TODO: add 2 raws and 2 colums to each set AND Wizard pst +var pst = { + 'P': [ + 0, 0, 0, 0, 0, 0, 0, 0, // + 78, 83, 86, 73, 102, 82, 85, 90, // + 7, 29, 21, 44, 40, 31, 44, 7, // + -17, 16, -2, 15, 14, 0, 15, -13, // + -26, 3, 10, 9, 6, 1, 0, -23, // + -22, 9, 5, -11, -10, -2, 3, -19, // + -31, 8, -7, -37, -36, -14, 3, -31, // + 0, 0, 0, 0, 0, 0, 0, 0 + ], // + 'N': [ + -66, -53, -75, -75, -10, -55, -58, -70, // + -3, -6, 100, -36, 4, 62, -4, -14, // + 10, 67, 1, 74, 73, 27, 62, -2, // + 24, 24, 45, 37, 33, 41, 25, 17, // + -1, 5, 31, 21, 22, 35, 2, 0, // + -18, 10, 13, 22, 18, 15, 11, -14, // + -23, -15, 2, 0, 2, 0, -23, -20, // + -74, -23, -26, -24, -19, -35, -22, -69 + ], // + 'B': [ + -59, -78, -82, -76, -23, -107, -37, -50, // + -11, 20, 35, -42, -39, 31, 2, -22, // + -9, 39, -32, 41, 52, -10, 28, -14, // + 25, 17, 20, 34, 26, 25, 15, 10, // + 13, 10, 17, 23, 17, 16, 0, 7, // + 14, 25, 24, 15, 8, 25, 20, 15, // + 19, 20, 11, 6, 7, 6, 20, 16, // + -7, 2, -15, -12, -14, -15, -10, -10 + ], // + 'R': [ + 35, 29, 33, 4, 37, 33, 56, 50, // + 55, 29, 56, 67, 55, 62, 34, 60, // + 19, 35, 28, 33, 45, 27, 25, 15, // + 0, 5, 16, 13, 18, -4, -9, -6, // + -28, -35, -16, -21, -13, -29, -46, -30, // + -42, -28, -42, -25, -25, -35, -26, -46, // + -53, -38, -31, -26, -29, -43, -44, -53, // + -30, -24, -18, 5, -2, -18, -31, -32 + ], // + 'Q': [ + 6, 1, -8, -104, 69, 24, 88, 26, // + 14, 32, 60, -10, 20, 76, 57, 24, // + -2, 43, 32, 60, 72, 63, 43, 2, // + 1, -16, 22, 17, 25, 20, -13, -6, // + -14, -15, -2, -5, -1, -10, -20, -22, // + -30, -6, -13, -11, -16, -11, -16, -27, // + -36, -18, 0, -19, -15, -15, -21, -38, // + -39, -30, -31, -13, -31, -36, -34, -42 + ], // + 'K': [ + 4, 54, 47, -99, -99, 60, 83, -62, // + -32, 10, 55, 56, 56, 55, 10, 3, // + -62, 12, -57, 44, -67, 28, 37, -31, // + -55, 50, 11, -4, -19, 13, 0, -49, // + -55, -43, -52, -28, -51, -47, -8, -50, // + -47, -42, -43, -79, -64, -32, -29, -32, // + -4, 3, -14, -50, -57, -18, 13, 4, // + 17, 30, -3, -14, 6, -1, 40, 18 + ], // +}; + +// recalculating Piece-Square raw into desk surrounded by 0-s +List padrow(List row, String k) { + var rowBody = [for (int x in row) x + piece[k]!]; + return [ + 0, + ...rowBody, + 0, + ]; +} + +void setPst() { + pst.forEach((key, item) { + final innerItem = [...List.filled(20, 0)]; + for (var i = 0; i < 8; i++) { + innerItem.addAll(padrow(item.getRange(i * 8, i * 8 + 8).toList(), key)); + } + innerItem.addAll(List.filled(20, 0)); + pst[key] = innerItem; + }); +} + +int getSeconds() { + return DateTime.now().millisecondsSinceEpoch ~/ 1000.toInt(); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Global constants +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Our board is represented as a 120 character string. The padding allows for +// fast detection of moves that don't stay within the board. + +//TODO: Tune globals for 10x10 field +final A1 = 91, H1 = 98, A8 = 21, H8 = 28; +final initial = (' \n' // 0 - 9 + ' \n' // 10 - 19 + ' rnbqkbnr\n' // 20 - 29 + ' pppppppp\n' // 30 - 39 + ' ........\n' // 40 - 49 + ' ........\n' // 50 - 59 + ' ........\n' // 60 - 69 + ' ........\n' // 70 - 79 + ' PPPPPPPP\n' // 80 - 89 + ' RNBQKBNR\n' // 90 - 99 + ' \n' // 100 -109 + ' \n' // 110 -119 + ); + +// Lists of possible moves for each piece type. +int N = -10, E = 1, S = 10, W = -1; +//TODO: add directions for Wizard +final Map> directions = { + 'P': [N, N + N, N + W, N + E], + 'N': [N + N + E, E + N + E, E + S + E, S + S + E, S + S + W, W + S + W, W + N + W, N + N + W], + 'B': [N + E, S + E, S + W, N + W], + 'R': [N, E, S, W], + 'Q': [N, E, S, W, N + E, S + E, S + W, N + W], + 'K': [N, E, S, W, N + E, S + E, S + W, N + W], + '.': [] +}; + +// Mate value must be greater than 8*queen + 2*(rook+knight+bishop) +// King value is set to twice this value such that if the opponent is +// 8 queens up, but we got the king, we still exceed MATE_VALUE. +// When a MATE is detected, we'll set the score to MATE_UPPER - plies to get there +// E.g. Mate in 3 will be MATE_UPPER - 6 +// TOD: replace mate with King capture event +final MATE_LOWER = piece['K']! - 10 * piece['Q']!; +final MATE_UPPER = piece['K']! + 10 * piece['Q']!; + +// The table size is the maximum number of elements in the transposition table. +final TABLE_SIZE = 1e7; + +// Constants for tuning search +final QS_LIMIT = 219, EVAL_ROUGHNESS = 13, DRAW_TEST = true; diff --git a/lib/src/ui.dart b/lib/src/ui.dart new file mode 100644 index 0000000..19371b4 --- /dev/null +++ b/lib/src/ui.dart @@ -0,0 +1,47 @@ +import 'package:dartsunfish/index.dart'; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// User interface +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class UI { + static int parse(String? c) { + var fil = c![0].codeUnits.first - 'a'.codeUnits.first; + var rank = int.parse(c[1]) - 1; + return A1 + fil - 10 * rank; + } + + static String render(int i) { + var rank = (i - A1) ~/ 10; + var fil = (i - A1) % 10; + return String.fromCharCodes([fil + 'a'.codeUnits.first]) + (-rank + 1).toString(); + } + + static void printBoard(Position pos) { + final uni_pieces = { + 'R': '♜', + 'N': '♞', + 'B': '♝', + 'Q': '♛', + 'K': '♚', + 'P': '♟', + 'r': '♖', + 'n': '♘', + 'b': '♗', + 'q': '♕', + 'k': '♔', + 'p': '♙', + '.': '·', + // ' ': ' ' + }; + print(''); + final bordChars = pos.board.split('\n'); + for (var i = 0; i < bordChars.length; i++) { + var row = bordChars[i]; + if (!row.isSpace) { + var pieces = row.split('').map((e) => e = e.isSpace ? '' : uni_pieces[e]!); + print('${(10 - i)}${pieces.join(' ')}'); + } + } + print(' a b c d e f g h \n\n'); + } +}