1from pychess.Utils.Cord import Cord
2from pychess.Utils.const import DROP, NORMAL_MOVE, PAWN, SITTUYINCHESS, QUEEN, KING, \
3    NULL_MOVE, WHITE, BLACK, W_OOO, W_OO, B_OOO, B_OO, QUEEN_CASTLE, FISCHERRANDOMCHESS,\
4    KING_CASTLE, CAMBODIANCHESS, ENPASSANT, PROMOTIONS, CASTLE_SAN, C1, G1, reprSign
5from pychess.Utils.lutils.lmovegen import newMove
6from .lutils import lmove
7
8
9class Move:
10
11    def __init__(self, cord0, cord1=None, board=None, promotion=None):
12        """ Inits a new highlevel Move object.
13            The object can be initialized in the follow ways:
14                Move(cord0, cord1, board, [promotionPiece])
15                Move(lovLevelMoveInt) """
16
17        if not cord1:
18            self.move = cord0
19            self.flag = self.move >> 12
20            self.cord0 = None if self.flag == DROP else Cord(lmove.FCORD(
21                self.move))
22            self.cord1 = Cord(lmove.TCORD(self.move))
23
24        else:
25            assert cord0 is not None and cord1 is not None, "cord0=%s, cord1=%s, board=%s" % (
26                cord0, cord1, board)
27            assert board[cord0] is not None, "cord0=%s, cord1=%s, board=%s" % (
28                cord0, cord1, board)
29            self.cord0 = cord0
30            self.cord1 = cord1
31            if not board:
32                raise ValueError(
33                    "Move needs a Board object in order to investigate flags")
34
35            self.flag = NORMAL_MOVE
36            if board[self.cord0].piece == PAWN and \
37                    self.cord1.cord in board.PROMOTION_ZONE[board.board.color] and \
38                    board.variant != SITTUYINCHESS:
39                if promotion is None:
40                    self.flag = lmove.FLAG_PIECE(QUEEN)
41                else:
42                    self.flag = lmove.FLAG_PIECE(promotion)
43
44            elif board[self.cord0].piece == PAWN and board.variant == SITTUYINCHESS:
45                if cord0 == cord1:
46                    # in place promotion
47                    self.flag = lmove.FLAG_PIECE(QUEEN)
48                elif board[self.cord1] is None and \
49                        (self.cord0.cord + self.cord1.cord) % 2 == 1 and \
50                        (self.cord0.cord in board.PROMOTION_ZONE[board.board.color] or board.board.pieceCount[board.color][PAWN] == 1):
51                    # queen move promotion
52                    self.flag = lmove.FLAG_PIECE(QUEEN)
53
54            elif board[self.cord0].piece == KING:
55                if self.cord0 == self.cord1:
56                    self.flag = NULL_MOVE
57
58                if self.cord0.x - self.cord1.x == 2 and board.variant not in (CAMBODIANCHESS, FISCHERRANDOMCHESS):
59                    self.flag = QUEEN_CASTLE if self.cord0.x == 4 else KING_CASTLE
60                elif self.cord0.x - self.cord1.x == -2 and board.variant not in (CAMBODIANCHESS, FISCHERRANDOMCHESS):
61                    self.flag = KING_CASTLE if self.cord0.x == 4 else QUEEN_CASTLE
62                elif board.variant != CAMBODIANCHESS:
63                    if (abs(self.cord0.x - self.cord1.x) > 1 and self.cord1.x == C1) or (
64                            board.board.ini_rooks[board.color][0] == self.cord1.cord and (
65                                (board.board.color == WHITE and board.board.castling & W_OOO) or (
66                            board.board.color == BLACK and board.board.castling & B_OOO))):
67                        self.flag = QUEEN_CASTLE
68                    elif (abs(self.cord0.x - self.cord1.x) > 1 and self.cord1.x == G1) or (
69                            board.board.ini_rooks[board.color][1] == self.cord1.cord and (
70                                (board.board.color == WHITE and board.board.castling & W_OO) or (
71                            board.board.color == BLACK and board.board.castling & B_OO))):
72                        self.flag = KING_CASTLE
73            elif board[self.cord0].piece == PAWN and \
74                    board[self.cord1] is None and \
75                    self.cord0.x != self.cord1.x and \
76                    self.cord0.y != self.cord1.y:
77                self.flag = ENPASSANT
78
79            self.move = newMove(self.cord0.cord, self.cord1.cord, self.flag)
80
81    def _get_cords(self):
82        return (self.cord0, self.cord1)
83
84    cords = property(_get_cords)
85
86    def _get_promotion(self):
87        if self.flag in PROMOTIONS:
88            return lmove.PROMOTE_PIECE(self.flag)
89        return None
90
91    promotion = property(_get_promotion)
92
93    def __repr__(self):
94        promotion = "=" + reprSign[lmove.PROMOTE_PIECE(self.flag)] \
95                    if self.flag in PROMOTIONS else ""
96
97        if self.flag == DROP:
98            piece = reprSign[lmove.FCORD(self.move)]
99            return piece + "@" + str(self.cord1) + promotion
100        else:
101            return str(self.cord0) + str(self.cord1) + promotion
102
103    def __eq__(self, other):
104        if isinstance(other, Move):
105            return self.move == other.move
106
107    def __hash__(self):
108        return hash(self.cords)
109
110    def is_capture(self, board):
111        return self.flag == ENPASSANT or \
112            board[self.cord1] is not None and \
113            self.flag != QUEEN_CASTLE and self.flag != KING_CASTLE
114
115    def as_uci(self):
116        move = "%s%s%s%s" % (self.cord0.cx, self.cord0.cy, self.cord1.cx, self.cord1.cy)
117        if self.flag in PROMOTIONS:
118            move += reprSign[lmove.PROMOTE_PIECE(self.flag)].lower()
119        return move
120
121# Parsers
122
123
124def listToMoves(board, mstrs, type=None, validate=False, ignoreErrors=False):
125    return [Move(move)
126            for move in lmove.listToMoves(board.board, mstrs, type, validate,
127                                          ignoreErrors)]
128
129
130def parseAny(board, algnot):
131    return Move(lmove.parseAny(board.board, algnot))
132
133
134def parseSAN(board, san):
135    """ Parse a Short/Abbreviated Algebraic Notation string """
136
137    return Move(lmove.parseSAN(board.board, san))
138
139
140def parseLAN(board, lan):
141    """ Parse a Long/Expanded Algebraic Notation string """
142
143    return Move(lmove.parseLAN(board.board, lan))
144
145
146def parseFAN(board, lan):
147    """ Parse a Long/Expanded Algebraic Notation string """
148
149    return Move(lmove.parseFAN(board.board, lan))
150
151
152def parseAN(board, an):
153    """ Parse an Algebraic Notation string """
154
155    return Move(lmove.parseAN(board.board, an))
156
157# Exporters
158
159
160def listToSan(board, moves):
161    return lmove.listToSan(board.board, (m.move for m in moves))
162
163
164def toAN(board, move, short=False, castleNotation=CASTLE_SAN):
165    """ Returns a Algebraic Notation string of a move
166        board should be prior to the move """
167
168    return lmove.toAN(board.board,
169                      move.move,
170                      short=short,
171                      castleNotation=castleNotation)
172
173
174def toSAN(board, move, localRepr=False):
175    """ Returns a Short/Abbreviated Algebraic Notation string of a move
176        The board should be prior to the move, board2 past.
177        If not board2, toSAN will not test mate """
178
179    return lmove.toSAN(board.board, move.move, localRepr)
180
181
182def toLAN(board, move):
183    """ Returns a Long/Expanded Algebraic Notation string of a move
184        board should be prior to the move """
185
186    return lmove.toLAN(board.board, move.move)
187
188
189def toFAN(board, move):
190    """ Returns a Figurine Algebraic Notation string of a move """
191
192    return lmove.toFAN(board.board, move.move)
193