package vn.kienutt.xiangqi; import java.util.ArrayList; import java.util.List; /** Rows 0..9, columns 0..8. Positive pieces are Red. */ public final class XiangqiEngine implements GamePosition { public static final int PAWN = 1, HORSE = 2, ELEPHANT = 3, ROOK = 4, CANNON = 5, KING = 6, ADVISOR = 7; public static final String INITIAL = "rheakaehr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RHEAKAEHR w"; private final int[] board; private final int turn, half; private final List history; private XiangqiEngine(int[] b, int turn, int half, List h) { this.board = b; this.turn = turn; this.half = half; this.history = h; } public static XiangqiEngine initial() { return fromFen(INITIAL); } public static XiangqiEngine fromFen(String fen) { String[] p = fen.trim().split(" +"), rows = p[0].split("/"); if (rows.length != 10) throw new IllegalArgumentException("Cần 10 hàng"); int[] b = new int[90]; int red = 0, black = 0; for (int r = 0; r < 10; r++) { int c = 0; for (char ch : rows[r].toCharArray()) { if (ch >= '1' && ch <= '9') c += ch - '0'; else { int t = " phercka".indexOf(Character.toLowerCase(ch)); if (t < 1 || c >= 9) throw new IllegalArgumentException("Quân sai"); int v = Character.isUpperCase(ch) ? t : -t; b[r * 9 + c++] = v; if (v == KING) red++; if (v == -KING) black++; } } if (c != 9) throw new IllegalArgumentException("Hàng sai"); } if (red != 1 || black != 1 || p.length < 2 || !(p[1].equals("w") || p[1].equals("b"))) throw new IllegalArgumentException("Thiếu tướng hoặc lượt đi"); XiangqiEngine x = new XiangqiEngine( b, p[1].equals("w") ? 1 : -1, p.length > 2 ? Integer.parseInt(p[2]) : 0, new ArrayList<>()); x.history.add(x.key()); return x; } public static XiangqiEngine restore(String text) { String[] p = text.split("\n"); XiangqiEngine x = fromFen(p[0]); if (p.length > 1) { x.history.clear(); for (int i = 1; i < p.length; i++) x.history.add(p[i]); if (!x.history.get(x.history.size() - 1).equals(x.key())) throw new IllegalArgumentException("Lịch sử sai"); } return x; } @Override public int rows() { return 10; } @Override public int columns() { return 9; } @Override public int side() { return turn; } @Override public int pieceAt(int square) { return board[square]; } @Override public boolean lossOnStalemate() { return true; } private static boolean inside(int r, int c) { return r >= 0 && r < 10 && c >= 0 && c < 9; } private static boolean palace(int r, int c, int side) { return c >= 3 && c <= 5 && (side == 1 ? r >= 7 && r <= 9 : r >= 0 && r <= 2); } private int screens(int from, int to) { int dr = Integer.signum(to / 9 - from / 9), dc = Integer.signum(to % 9 - from % 9), n = 0; for (int r = from / 9 + dr, c = from % 9 + dc; r != to / 9 || c != to % 9; r += dr, c += dc) if (board[r * 9 + c] != 0) n++; return n; } private boolean geometric(int from, int to, boolean attack) { if (from == to) return false; int p = board[from], side = Integer.signum(p), t = Math.abs(p), r = from / 9, c = from % 9, rr = to / 9, cc = to % 9, dr = rr - r, dc = cc - c; switch (t) { case PAWN: return dr == -side && dc == 0 || dr == 0 && Math.abs(dc) == 1 && (side == 1 ? r <= 4 : r >= 5); case HORSE: if (Math.abs(dr) == 2 && Math.abs(dc) == 1) return board[(r + Integer.signum(dr)) * 9 + c] == 0; if (Math.abs(dr) == 1 && Math.abs(dc) == 2) return board[r * 9 + c + Integer.signum(dc)] == 0; return false; case ELEPHANT: return Math.abs(dr) == 2 && Math.abs(dc) == 2 && (side == 1 ? rr >= 5 : rr <= 4) && board[(r + dr / 2) * 9 + c + dc / 2] == 0; case ADVISOR: return Math.abs(dr) == 1 && Math.abs(dc) == 1 && palace(rr, cc, side); case KING: if (attack && dc == 0 && board[to] == -side * KING && screens(from, to) == 0) return true; return Math.abs(dr) + Math.abs(dc) == 1 && palace(rr, cc, side); case ROOK: return (dr == 0 || dc == 0) && screens(from, to) == 0; case CANNON: if (dr != 0 && dc != 0) return false; return screens(from, to) == (board[to] == 0 && !attack ? 0 : 1); default: return false; } } private boolean check(int side) { int king = -1; for (int i = 0; i < 90; i++) if (board[i] == side * KING) king = i; if (king < 0) return true; for (int i = 0; i < 90; i++) if (board[i] * side < 0 && geometric(i, king, true)) return true; return false; } @Override public boolean inCheck() { return check(turn); } @Override public List legalMoves() { List out = new ArrayList<>(); for (int from = 0; from < 90; from++) if (board[from] * turn > 0) for (int to = 0; to < 90; to++) { if (board[to] * turn > 0 || Math.abs(board[to]) == KING || !geometric(from, to, false)) continue; Move m = new Move(from, to); if (!apply(m, false).check(turn)) out.add(m); } return out; } private XiangqiEngine apply(Move m, boolean remember) { int[] b = board.clone(); int captured = b[m.to]; b[m.to] = b[m.from]; b[m.from] = 0; List h = remember ? new ArrayList<>(history) : history; XiangqiEngine x = new XiangqiEngine(b, -turn, captured != 0 ? 0 : half + 1, h); if (remember) h.add(x.key()); return x; } @Override public XiangqiEngine after(Move move) { return apply(move, true); } private String placement() { StringBuilder s = new StringBuilder(); for (int r = 0; r < 10; r++) { if (r > 0) s.append('/'); int empty = 0; for (int c = 0; c < 9; c++) { int p = board[r * 9 + c]; if (p == 0) empty++; else { if (empty > 0) { s.append(empty); empty = 0; } char ch = " phercka".charAt(Math.abs(p)); s.append(p > 0 ? Character.toUpperCase(ch) : ch); } } if (empty > 0) s.append(empty); } return s.toString(); } private String key() { return placement() + " " + (turn == 1 ? "w" : "b"); } @Override public String encode() { return key() + " " + half + "\n" + String.join("\n", history); } @Override public boolean drawn() { int count = 0; for (String s : history) if (s.equals(key())) count++; return count >= 3 || half >= 120; } @Override public int evaluate() { int[] value = {0, 100, 400, 200, 900, 450, 0, 200}; int sum = 0; for (int i = 0; i < 90; i++) { int p = board[i], t = Math.abs(p); if (t == 0) continue; int bonus = 0; if (t == PAWN) { boolean crossed = p > 0 ? i / 9 <= 4 : i / 9 >= 5; bonus = (crossed ? 80 : 0) + (p > 0 ? 9 - i / 9 : i / 9) * 5; } if (t == HORSE || t == CANNON) bonus = 12 - Math.abs(i % 9 - 4) * 3; sum += (p > 0 ? 1 : -1) * (value[t] + bonus); } return turn * sum; } }