1""" This module contains chess logic functins for the pychess client. They are
2    based upon the lutils modules, but supports standard object types and is
3    therefore not as fast. """
4
5from .lutils import lmovegen
6from .lutils.validator import validateMove
7from .lutils.lmove import FCORD, TCORD
8from .lutils import ldraw
9from .Cord import Cord
10from .Move import Move
11from .const import LOSERSCHESS, WHITE, WHITEWON, BLACKWON, WON_NOMATERIAL, KING, HORDECHESS, \
12    SUICIDECHESS, GIVEAWAYCHESS, ATOMICCHESS, WON_KINGEXPLODE, KINGOFTHEHILLCHESS, BLACK, DRAW, \
13    CRAZYHOUSECHESS, WON_KINGINCENTER, THREECHECKCHESS, WON_THREECHECK, WON_MATE, DRAW_STALEMATE, \
14    DRAW_INSUFFICIENT, DRAW_EQUALMATERIAL, WON_LESSMATERIAL, WON_WIPEOUT, DRAW_REPETITION, \
15    WON_KINGINEIGHTROW, RACINGKINGSCHESS, DRAW_50MOVES, DRAW_KINGSINEIGHTROW, RUNNING, ENPASSANT, UNKNOWN_REASON
16
17from .lutils.bitboard import iterBits
18from .lutils.attack import getAttacks
19from pychess.Variants.suicide import pieceCount
20from pychess.Variants.losers import testKingOnly
21from pychess.Variants.atomic import kingExplode
22from pychess.Variants.kingofthehill import testKingInCenter
23from pychess.Variants.threecheck import checkCount
24from pychess.Variants.racingkings import testKingInEightRow, test2KingInEightRow
25
26
27def getDestinationCords(board, cord):
28    tcords = []
29    for move in lmovegen.genAllMoves(board.board):
30        if FCORD(move) == cord.cord:
31            if not board.board.willLeaveInCheck(move):
32                tcords.append(Cord(TCORD(move)))
33    return tcords
34
35
36def isClaimableDraw(board):
37    lboard = board.board
38    if lboard.repetitionCount() >= 3:
39        return True
40    if ldraw.testFifty(lboard):
41        return True
42    return False
43
44
45def playerHasMatingMaterial(board, playercolor):
46    if board.variant == CRAZYHOUSECHESS:
47        return True
48    lboard = board.board
49    return ldraw.testPlayerMatingMaterial(lboard, playercolor)
50
51
52def getStatus(board):
53    lboard = board.board
54
55    if board.variant == LOSERSCHESS:
56        if testKingOnly(lboard):
57            if board.color == WHITE:
58                status = WHITEWON
59            else:
60                status = BLACKWON
61            return status, WON_NOMATERIAL
62    elif board.variant == SUICIDECHESS or board.variant == GIVEAWAYCHESS:
63        if pieceCount(lboard, lboard.color) == 0:
64            if board.color == WHITE:
65                status = WHITEWON
66            else:
67                status = BLACKWON
68            return status, WON_NOMATERIAL
69    elif board.variant == HORDECHESS:
70        if pieceCount(lboard, lboard.color) == 0 and board.color == WHITE:
71            status = BLACKWON
72            return status, WON_WIPEOUT
73    elif board.variant == ATOMICCHESS:
74        if lboard.boards[board.color][KING] == 0:
75            if board.color == WHITE:
76                status = BLACKWON
77            else:
78                status = WHITEWON
79            return status, WON_KINGEXPLODE
80    elif board.variant == KINGOFTHEHILLCHESS:
81        if testKingInCenter(lboard):
82            if board.color == BLACK:
83                status = WHITEWON
84            else:
85                status = BLACKWON
86            return status, WON_KINGINCENTER
87    elif board.variant == THREECHECKCHESS:
88        if checkCount(lboard, lboard.color) == 3:
89            if board.color == BLACK:
90                status = WHITEWON
91            else:
92                status = BLACKWON
93            return status, WON_THREECHECK
94    elif board.variant == RACINGKINGSCHESS:
95        if test2KingInEightRow(lboard):
96            return DRAW, DRAW_KINGSINEIGHTROW
97        elif testKingInEightRow(lboard):
98            can_save = False
99            for move in lmovegen.genAllMoves(lboard):
100                if lboard.willGiveCheck(move) or lboard.willLeaveInCheck(move):
101                    continue
102
103                lboard.applyMove(move)
104                if testKingInEightRow(lboard):
105                    can_save = True
106                    lboard.popMove()
107                    break
108                lboard.popMove()
109            if not can_save:
110                if board.color == BLACK:
111                    status = WHITEWON
112                else:
113                    status = BLACKWON
114                return status, WON_KINGINEIGHTROW
115    else:
116        if ldraw.testMaterial(lboard):
117            return DRAW, DRAW_INSUFFICIENT
118
119    hasMove = False
120    for move in lmovegen.genAllMoves(lboard):
121        if board.variant == ATOMICCHESS:
122            if kingExplode(lboard, move, 1 - board.color) and not kingExplode(
123                    lboard, move, board.color):
124                hasMove = True
125                break
126            elif kingExplode(lboard, move, board.color):
127                continue
128        lboard.applyMove(move)
129        if lboard.opIsChecked():
130            lboard.popMove()
131            continue
132        hasMove = True
133        lboard.popMove()
134        break
135
136    if not hasMove:
137        if lboard.isChecked():
138            if board.variant == LOSERSCHESS:
139                if board.color == WHITE:
140                    status = WHITEWON
141                else:
142                    status = BLACKWON
143            else:
144                if board.color == WHITE:
145                    status = BLACKWON
146                else:
147                    status = WHITEWON
148            return status, WON_MATE
149        else:
150            if board.variant == LOSERSCHESS or board.variant == GIVEAWAYCHESS:
151                if board.color == WHITE:
152                    status = WHITEWON
153                else:
154                    status = BLACKWON
155                return status, DRAW_STALEMATE
156            elif board.variant == SUICIDECHESS:
157                if pieceCount(lboard, WHITE) == pieceCount(lboard, BLACK):
158                    return status, DRAW_EQUALMATERIAL
159                else:
160                    if board.color == WHITE and pieceCount(
161                            lboard, WHITE) < pieceCount(lboard, BLACK):
162                        status = WHITEWON
163                    else:
164                        status = BLACKWON
165                    return status, WON_LESSMATERIAL
166            else:
167                return DRAW, DRAW_STALEMATE
168
169    if lboard.repetitionCount() >= 3:
170        return DRAW, DRAW_REPETITION
171
172    if ldraw.testFifty(lboard):
173        return DRAW, DRAW_50MOVES
174
175    return RUNNING, UNKNOWN_REASON
176
177
178def standard_validate(board, move):
179    return validateMove(board.board, move.move) and \
180        not board.board.willLeaveInCheck(move.move)
181
182
183def validate(board, move):
184    if board.variant == LOSERSCHESS:
185        capture = move.flag == ENPASSANT or board[move.cord1] is not None
186        if capture:
187            return standard_validate(board, move)
188        else:
189            can_capture = False
190            can_escape_with_capture = False
191            ischecked = board.board.isChecked()
192            for c in lmovegen.genCaptures(board.board):
193                if board.board.willLeaveInCheck(c):
194                    continue
195                else:
196                    can_capture = True
197                    if ischecked:
198                        can_escape_with_capture = True
199                    break
200            if can_capture:
201                if ischecked and not can_escape_with_capture:
202                    return standard_validate(board, move)
203                else:
204                    return False
205            else:
206                return standard_validate(board, move)
207    elif board.variant == SUICIDECHESS:
208        capture = move.flag == ENPASSANT or board[move.cord1] is not None
209        if capture:
210            return standard_validate(board, move)
211        else:
212            can_capture = False
213            for c in lmovegen.genCaptures(board.board):
214                can_capture = True
215            if can_capture:
216                return False
217            else:
218                return standard_validate(board, move)
219    elif board.variant == ATOMICCHESS:
220        # Moves exploding our king are not allowed
221        if kingExplode(board.board, move.move, board.color):
222            return False
223        # Exploding oppont king takes precedence over mate
224        elif kingExplode(board.board, move.move, 1 -
225                         board.color) and validateMove(board.board, move.move):
226            return True
227        else:
228            return standard_validate(board, move)
229    elif board.variant == RACINGKINGSCHESS:
230        # Giving check is forbidden
231        if board.board.willGiveCheck(move.move):
232            return False
233        else:
234            return standard_validate(board, move)
235    else:
236        return standard_validate(board, move)
237
238
239def getMoveKillingKing(board):
240    """ Returns a move from the current color, able to capture the opponent
241        king """
242
243    lboard = board.board
244    color = lboard.color
245    opking = lboard.kings[1 - color]
246
247    for cord in iterBits(getAttacks(lboard, opking, color)):
248        return Move(Cord(cord), Cord(opking), board)
249
250
251def genCastles(board):
252    for move in lmovegen.genCastles(board.board):
253        yield Move(move)
254
255
256def legalMoveCount(board):
257    moves = 0
258    for move in lmovegen.genAllMoves(board.board):
259        if not board.board.willLeaveInCheck(move):
260            moves += 1
261    return moves
262