package vn.kienutt.chess; import java.util.ArrayList; import java.util.List; /** Immutable board snapshots. Positive pieces are White; row 0 is rank 8. */ public final class ChessEngine implements GamePosition { public static final int PAWN = 1, KNIGHT = 2, BISHOP = 3, ROOK = 4, QUEEN = 5, KING = 6; public static final String INITIAL = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; private final int[] board; private final int turn, rights, ep, half, full; private final List history; private ChessEngine( int[] board, int turn, int rights, int ep, int half, int full, List history) { this.board = board; this.turn = turn; this.rights = rights; this.ep = ep; this.half = half; this.full = full; this.history = history; } public static ChessEngine initial() { return fromFen(INITIAL); } public static ChessEngine fromFen(String fen) { String[] parts = fen.trim().split(" +"); if (parts.length < 4) throw new IllegalArgumentException("FEN thiếu trường"); String[] rows = parts[0].split("/"); if (rows.length != 8) throw new IllegalArgumentException("FEN cần 8 hàng"); int[] b = new int[64]; int whiteKings = 0, blackKings = 0; for (int r = 0; r < 8; r++) { int c = 0; for (char ch : rows[r].toCharArray()) { if (ch >= '1' && ch <= '8') c += ch - '0'; else { int t = " pnbrqk".indexOf(Character.toLowerCase(ch)); if (t < 1 || c >= 8) throw new IllegalArgumentException("Quân FEN sai"); b[r * 8 + c++] = Character.isUpperCase(ch) ? t : -t; if (t == KING) { if (Character.isUpperCase(ch)) whiteKings++; else blackKings++; } } } if (c != 8) throw new IllegalArgumentException("Hàng FEN sai"); } if (whiteKings != 1 || blackKings != 1 || !(parts[1].equals("w") || parts[1].equals("b"))) throw new IllegalArgumentException("FEN cần hai vua và lượt đi"); int cr = 0; if (parts[2].contains("K")) cr |= 1; if (parts[2].contains("Q")) cr |= 2; if (parts[2].contains("k")) cr |= 4; if (parts[2].contains("q")) cr |= 8; int ep = parts[3].equals("-") ? -1 : square(parts[3]); ChessEngine p = new ChessEngine( b, parts[1].equals("w") ? 1 : -1, cr, ep, parts.length > 4 ? Integer.parseInt(parts[4]) : 0, parts.length > 5 ? Integer.parseInt(parts[5]) : 1, new ArrayList<>()); if (p.half < 0 || p.full < 1) throw new IllegalArgumentException("Bộ đếm FEN sai"); p.history.add(p.key()); return p; } public static int square(String name) { if (!name.matches("[a-h][1-8]")) throw new IllegalArgumentException("Ô cờ sai"); return (8 - (name.charAt(1) - '0')) * 8 + name.charAt(0) - 'a'; } public static String name(int sq) { return "" + (char) ('a' + sq % 8) + (8 - sq / 8); } public static String uci(Move m) { return name(m.from) + name(m.to) + (m.promotion == 0 ? "" : " pnbrqk".charAt(m.promotion)); } public static ChessEngine restore(String text) { String[] lines = text.split("\n"); ChessEngine p = fromFen(lines[0]); if (lines.length > 1) { p.history.clear(); for (int i = 1; i < lines.length; i++) p.history.add(lines[i]); if (!p.history.get(p.history.size() - 1).equals(p.key())) throw new IllegalArgumentException("Lịch sử sai"); } return p; } @Override public int rows() { return 8; } @Override public int columns() { return 8; } @Override public int side() { return turn; } @Override public int pieceAt(int sq) { return board[sq]; } @Override public boolean lossOnStalemate() { return false; } private static boolean inside(int r, int c) { return r >= 0 && r < 8 && c >= 0 && c < 8; } private void add(List out, int from, int to) { if (Math.abs(board[from]) == PAWN && (to / 8 == 0 || to / 8 == 7)) for (int t : new int[] {QUEEN, ROOK, BISHOP, KNIGHT}) out.add(new Move(from, to, t)); else out.add(new Move(from, to)); } @Override public List legalMoves() { List pseudo = new ArrayList<>(), legal = new ArrayList<>(); for (int from = 0; from < 64; from++) { int p = board[from]; if (p * turn <= 0) continue; int r = from / 8, c = from % 8, t = Math.abs(p); if (t == PAWN) { int nr = r - turn; if (inside(nr, c) && board[nr * 8 + c] == 0) { add(pseudo, from, nr * 8 + c); int start = turn == 1 ? 6 : 1; if (r == start && board[(r - 2 * turn) * 8 + c] == 0) add(pseudo, from, (r - 2 * turn) * 8 + c); } for (int dc : new int[] {-1, 1}) if (inside(nr, c + dc)) { int to = nr * 8 + c + dc; if ((board[to] * turn < 0 && Math.abs(board[to]) != KING) || (to == ep && board[to] == 0 && board[to + turn * 8] == -turn * PAWN)) add(pseudo, from, to); } } else if (t == KNIGHT) { for (int[] d : new int[][] {{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1}}) { int rr = r + d[0], cc = c + d[1]; if (inside(rr, cc) && board[rr * 8 + cc] * turn <= 0 && Math.abs(board[rr * 8 + cc]) != KING) add(pseudo, from, rr * 8 + cc); } } else if (t == KING) { for (int dr = -1; dr <= 1; dr++) for (int dc = -1; dc <= 1; dc++) if (dr != 0 || dc != 0) { int rr = r + dr, cc = c + dc; if (inside(rr, cc) && board[rr * 8 + cc] * turn <= 0 && Math.abs(board[rr * 8 + cc]) != KING) add(pseudo, from, rr * 8 + cc); } int home = turn == 1 ? 60 : 4, ks = turn == 1 ? 1 : 4, qs = turn == 1 ? 2 : 8; if (from == home && !check(turn)) { if ((rights & ks) != 0 && board[home + 3] == turn * ROOK && board[home + 1] == 0 && board[home + 2] == 0 && !attacked(home + 1, -turn) && !attacked(home + 2, -turn)) add(pseudo, from, home + 2); if ((rights & qs) != 0 && board[home - 4] == turn * ROOK && board[home - 1] == 0 && board[home - 2] == 0 && board[home - 3] == 0 && !attacked(home - 1, -turn) && !attacked(home - 2, -turn)) add(pseudo, from, home - 2); } } else { for (int dr = -1; dr <= 1; dr++) for (int dc = -1; dc <= 1; dc++) { if (dr == 0 && dc == 0) continue; boolean diagonal = dr != 0 && dc != 0; if ((t == BISHOP && !diagonal) || (t == ROOK && diagonal)) continue; for (int rr = r + dr, cc = c + dc; inside(rr, cc); rr += dr, cc += dc) { int to = rr * 8 + cc, target = board[to]; if (target * turn > 0) break; if (Math.abs(target) != KING) add(pseudo, from, to); if (target != 0) break; } } } } for (Move m : pseudo) if (!apply(m, false).check(turn)) legal.add(m); return legal; } private boolean attacked(int sq, int side) { int r = sq / 8, c = sq % 8; for (int dc : new int[] {-1, 1}) if (inside(r + side, c + dc) && board[(r + side) * 8 + c + dc] == side * PAWN) return true; for (int[] d : new int[][] {{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1}}) if (inside(r + d[0], c + d[1]) && board[(r + d[0]) * 8 + c + d[1]] == side * KNIGHT) return true; for (int dr = -1; dr <= 1; dr++) for (int dc = -1; dc <= 1; dc++) { if (dr == 0 && dc == 0) continue; for (int rr = r + dr, cc = c + dc, n = 1; inside(rr, cc); rr += dr, cc += dc, n++) { int p = board[rr * 8 + cc]; if (p == 0) continue; if (p * side > 0) { int t = Math.abs(p); if (t == QUEEN || (t == KING && n == 1) || (t == BISHOP && dr != 0 && dc != 0) || (t == ROOK && (dr == 0 || dc == 0))) return true; } break; } } return false; } private boolean check(int side) { for (int i = 0; i < 64; i++) if (board[i] == side * KING) return attacked(i, -side); return true; } @Override public boolean inCheck() { return check(turn); } private ChessEngine apply(Move m, boolean remember) { int[] b = board.clone(); int piece = b[m.from], captured = b[m.to], nr = rights, newEp = -1; if (Math.abs(piece) == PAWN && m.to == ep && captured == 0 && m.from % 8 != m.to % 8) { captured = b[m.to + turn * 8]; b[m.to + turn * 8] = 0; } b[m.from] = 0; b[m.to] = m.promotion == 0 ? piece : turn * m.promotion; if (Math.abs(piece) == KING) { nr &= turn == 1 ? ~3 : ~12; if (Math.abs(m.to - m.from) == 2) { int rookFrom = m.to > m.from ? m.from + 3 : m.from - 4, rookTo = m.to > m.from ? m.from + 1 : m.from - 1; b[rookTo] = b[rookFrom]; b[rookFrom] = 0; } } if (m.from == 63 || m.to == 63) nr &= ~1; if (m.from == 56 || m.to == 56) nr &= ~2; if (m.from == 7 || m.to == 7) nr &= ~4; if (m.from == 0 || m.to == 0) nr &= ~8; if (Math.abs(piece) == PAWN && Math.abs(m.to - m.from) == 16) newEp = (m.from + m.to) / 2; List hist = remember ? new ArrayList<>(history) : history; ChessEngine next = new ChessEngine( b, -turn, nr, newEp, Math.abs(piece) == PAWN || captured != 0 ? 0 : half + 1, full + (turn == -1 ? 1 : 0), hist); if (remember) hist.add(next.key()); return next; } @Override public ChessEngine after(Move m) { return apply(m, true); } public ChessEngine play(String uci) { for (Move m : legalMoves()) if (uci(m).equals(uci)) return after(m); throw new IllegalArgumentException("Nước không hợp lệ: " + uci); } private String placement() { StringBuilder s = new StringBuilder(); for (int r = 0; r < 8; r++) { if (r > 0) s.append('/'); int n = 0; for (int c = 0; c < 8; c++) { int p = board[r * 8 + c]; if (p == 0) n++; else { if (n > 0) { s.append(n); n = 0; } char ch = " pnbrqk".charAt(Math.abs(p)); s.append(p > 0 ? Character.toUpperCase(ch) : ch); } } if (n > 0) s.append(n); } return s.toString(); } private String castle() { String s = ""; if ((rights & 1) != 0) s += "K"; if ((rights & 2) != 0) s += "Q"; if ((rights & 4) != 0) s += "k"; if ((rights & 8) != 0) s += "q"; return s.isEmpty() ? "-" : s; } private String key() { return placement() + " " + (turn == 1 ? "w" : "b") + " " + castle() + " " + (ep < 0 ? "-" : name(ep)); } public String fen() { return key() + " " + half + " " + full; } @Override public String encode() { return fen() + "\n" + String.join("\n", history); } @Override public boolean drawn() { if (half >= 100) return true; int repeats = 0; for (String h : history) if (h.equals(key())) repeats++; if (repeats >= 3) return true; int minors = 0, bishopColor = -1; boolean bishopsOnly = true; for (int i = 0; i < 64; i++) { int t = Math.abs(board[i]); if (t == 0 || t == KING) continue; if (t == PAWN || t == ROOK || t == QUEEN) return false; minors++; if (t == KNIGHT) bishopsOnly = false; else { int color = (i / 8 + i % 8) % 2; if (bishopColor == -1) bishopColor = color; else if (bishopColor != color) bishopsOnly = false; } } return minors <= 1 || bishopsOnly; } @Override public int evaluate() { int[] value = {0, 100, 320, 335, 500, 900, 0}; int sum = 0; for (int i = 0; i < 64; i++) { int p = board[i], t = Math.abs(p); if (t == 0) continue; int center = 14 - (Math.abs(2 * (i % 8) - 7) + Math.abs(2 * (i / 8) - 7)); int bonus = (t == KNIGHT || t == BISHOP) ? center * 3 : 0; if (t == PAWN) bonus += (p > 0 ? 6 - i / 8 : i / 8 - 1) * 7; sum += (p > 0 ? 1 : -1) * (value[t] + bonus); } return turn * sum; } }