248 lines
6.7 KiB
Dart
248 lines
6.7 KiB
Dart
import 'package:boardgames_core/commons.dart';
|
|
import 'package:boardgames_core/core.dart';
|
|
import 'package:logging/logging.dart';
|
|
|
|
// 4 points to check if checker can move
|
|
final List<CellPosition> _jumps = [
|
|
CellPosition(-2, -2),
|
|
CellPosition(2, -2),
|
|
CellPosition(-2, 2),
|
|
CellPosition(2, 2),
|
|
];
|
|
final List<CellPosition> _dirs = [
|
|
CellPosition(-1, -1),
|
|
CellPosition(1, -1),
|
|
CellPosition(-1, 1),
|
|
CellPosition(1, 1),
|
|
];
|
|
|
|
class Checker extends Element<Rectangular> {
|
|
static final Logger log = Logger("Checker");
|
|
@override
|
|
bool interactable = true;
|
|
@override
|
|
bool canMove = true;
|
|
List<Checker> killed = [];
|
|
|
|
Checker(super.position);
|
|
|
|
/// Moving the pawn from->to position
|
|
/// Returns null if move not possible
|
|
/// or List of killed pawns (can be empty if moving to empty cell)
|
|
List<Checker>? _venturePosition(CellPosition from, CellPosition to) {
|
|
if (board == null || !board!.geometry.isOnBoard(to)) {
|
|
// Cannot move outside the board
|
|
log.finer("Cannot move outside the board $to");
|
|
return null;
|
|
}
|
|
final elems = board!.getElementsAtPosition(to);
|
|
|
|
// Cannot move where occupied
|
|
if (elems.isNotEmpty) {
|
|
log.finer("Cannot move where occupied");
|
|
return null;
|
|
}
|
|
final vector = from - to;
|
|
|
|
// TODO: Calculate settings.canMoveBackwards depending on player? Mirror board
|
|
// Need to decide how to render board
|
|
// final settings = board!.settings as CheckerSettings;
|
|
final distance = from.distance(to).floor();
|
|
|
|
// Can move to closest adjustent cell if empty (did check elems.isNotEmpty above)
|
|
if (distance == 1) {
|
|
// Can only move diagonal
|
|
return vector.column != 0 && vector.row != 0 ? [] : null;
|
|
}
|
|
log.finest("Starting tracing back from $to back to $from");
|
|
// If distance is greater than one cell then we need to trace back to selected pawn
|
|
final owner = this.getOwners().firstOrNull;
|
|
List<CellPosition> queue = [to];
|
|
Set<CellPosition> visited = new Set();
|
|
// TODO: Revisit: Assuming only one path possible in Checkers game
|
|
visited.add(to);
|
|
Map<CellPosition, CellPosition> path = new Map();
|
|
Map<CellPosition, Checker> pawns = new Map();
|
|
List<Checker> _traceBack(CellPosition? origin) {
|
|
List<Checker> res = [];
|
|
CellPosition? it = origin;
|
|
|
|
log.finest("======");
|
|
log.finest(path);
|
|
log.finest(pawns);
|
|
log.finest("======");
|
|
|
|
do {
|
|
if (pawns.containsKey(it)) {
|
|
res.add(pawns[it]!);
|
|
}
|
|
it = path[it];
|
|
} while (it != null);
|
|
|
|
return res;
|
|
}
|
|
|
|
while (queue.length > 0) {
|
|
final origin = queue.removeLast();
|
|
|
|
// Exit criteria - we traced back our pawn
|
|
if (origin == from) {
|
|
log.finest("Trace back succeeded!");
|
|
return _traceBack(origin);
|
|
}
|
|
for (final vector in _jumps) {
|
|
CellPosition checkPos = origin + vector;
|
|
|
|
if (!board!.geometry.isOnBoard(checkPos)) {
|
|
// Cannot move outside the board
|
|
continue;
|
|
}
|
|
|
|
if (visited.contains(checkPos)) {
|
|
log.finest("Already visited position $checkPos");
|
|
continue;
|
|
}
|
|
visited.add(checkPos);
|
|
|
|
var elems = board!.getElementsAtPosition(checkPos);
|
|
if (elems.isNotEmpty && checkPos != from) {
|
|
// Cannot move if position occupied
|
|
// allow original position to pass to fall into exit condition above
|
|
log.finest("Checker is blocking $checkPos, skipping");
|
|
continue;
|
|
}
|
|
// Check if we attack someone
|
|
final jumpPos = checkPos - vector.decreaseBy(1);
|
|
elems = board!.getElementsAtPosition(jumpPos);
|
|
final checker = elems.firstOrNull as Checker?;
|
|
|
|
if (checker == null) {
|
|
// Skip if no checkers on the path
|
|
log.finest("cannot jump over empty cell at $jumpPos, skipping");
|
|
continue;
|
|
}
|
|
// Check if it is not ours checker
|
|
if (owner == null || checker.isOwner(owner)) {
|
|
// Cannot attack yourself or jump over empty cell
|
|
log.finest(
|
|
"Player#${owner == null ? "null" : owner.id} at $jumpPos owns this pawn, skipping");
|
|
continue;
|
|
} else {
|
|
log.finest("You ate checker: $checker ($jumpPos)");
|
|
}
|
|
log.finest("Can jump to $checkPos (over $jumpPos), will check it too");
|
|
path[checkPos] = origin;
|
|
pawns[checkPos] = checker;
|
|
queue.add(checkPos);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
bool canMoveTo(CellPosition position) {
|
|
killed.clear();
|
|
bool globalCanMove = super.canMoveTo(position);
|
|
|
|
(board!.game as Checkers).bank.clear();
|
|
if (!globalCanMove) {
|
|
return false;
|
|
}
|
|
final res = this._venturePosition(this.position, position);
|
|
|
|
if (res == null) {
|
|
return false;
|
|
}
|
|
killed = res;
|
|
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
void moveTo(CellPosition newPosition) {
|
|
super.moveTo(newPosition);
|
|
board!.game.currentPlayer!.jail.addAll(killed);
|
|
for (var elem in killed) {
|
|
board!.elements.remove(elem);
|
|
}
|
|
killed.clear();
|
|
}
|
|
|
|
@override
|
|
canSharePosition(Element element) {
|
|
// no shared position in checkers
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
List<CellPosition> getAvailableMoves() {
|
|
// TODO: implement getAvailableMoves
|
|
throw UnimplementedError();
|
|
}
|
|
}
|
|
|
|
class CheckerSettings extends GameSettings {
|
|
bool canMoveBackwards = false;
|
|
bool canDenyChainAttacks = false;
|
|
bool canSwapToQueen = true;
|
|
bool queenCanMoveWholeBoard = true;
|
|
}
|
|
|
|
// 8x8 board, 24 checkers (12 per player)
|
|
class Checkers extends Game<Rectangular, Checker, CheckerSettings> {
|
|
@override
|
|
late Board<Rectangular, Checker, CheckerSettings> board;
|
|
@override
|
|
late int maximumPlayers;
|
|
@override
|
|
late int requiredPlayers;
|
|
@override
|
|
late CheckerSettings settings = CheckerSettings();
|
|
final List<Checker> bank = [];
|
|
|
|
Checkers() : super() {
|
|
board = Board(Rectangular(8, 8));
|
|
maximumPlayers = 2;
|
|
requiredPlayers = 2;
|
|
// Adding players
|
|
players.add(new Player());
|
|
players.add(new Player());
|
|
|
|
board.setGame(this);
|
|
|
|
// Filling board
|
|
int col = -1;
|
|
int row = 0;
|
|
for (int i = 0; i < 24; i++) {
|
|
if (i > 0 && i % 4 == 0) {
|
|
row += 1;
|
|
col = row % 2 == 0 ? 1 : 0;
|
|
} else {
|
|
col += 2;
|
|
}
|
|
final position = CellPosition(col, row);
|
|
final checker = new Checker(position);
|
|
|
|
board.addElementOnBoard(checker);
|
|
if (i < 12) {
|
|
checker.addOwner(players[0]);
|
|
} else {
|
|
checker.addOwner(players[1]);
|
|
}
|
|
if (i == 11) {
|
|
row += 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
List<Player> getWinners() {
|
|
return [currentPlayer!];
|
|
}
|
|
|
|
@override
|
|
bool hasGameFinished() {
|
|
return board.elements.map((e) => e.getOwners().first).toSet().length == 1;
|
|
}
|
|
}
|