Untitled
unknown
python
a year ago
19 kB
8
Indexable
import itertools import os import json from gen_config import options as GAMEMODE_OPTIONS DIM = 8 class Gamemode: # allows reference to gamemode e.g. Gamemode.default default = "regular" king_of_the_hill = "king of the hill" class Position: def __init__(self, x:int, y:int, gamestate): self.x = x self.y = y self.piece_type = gamestate.get_piece(self) @property def pos(self) -> tuple: return self.x, self.y def __str__(self): return str(self.pos) def __eq__(self, other): if isinstance(other, Position) and self.pos == other.pos: return True return False class Move: def __init__(self, start:Position, end:Position, gamestate): #// self.startx, self.starty = start #// self.endx, self.endy = end self.start, self.end = start, end self.start_piece = gamestate.get_piece(start) self.end_piece = gamestate.get_piece(end) #// self.id = int(str(self.startx)+str(self.starty)+str(self.endx)+str(self.endy)) #// def __eq__(self, other) -> bool: #// if isinstance(other, Move) and self.id == other.id: #// return True #// return False __eq__ = lambda self, other: True if isinstance(other, Move) and self.id == other.id else False # overrides the default __eq__ and compares the ids # of the 2 Move objects #// def __str__(self) -> str: #// return str([self.start.piece_type, self.start.x, self.start.y, self.end.x, self.end.y, self.end.piece_type]) __str__ = lambda self: str([self.start.x, self.start.y, self.start.piece_type, self.end.x, self.end.y, self.end.piece_type]) @property def id(self) -> str: return str(self.start.x)+str(self.start.y)+str(self.end.x) + str(self.end.y) @property def capturing_move(self)->bool: inverse = {"w":"b", "b":"w", "-":"-"} if self.start_piece[0] == inverse.get(self.end_piece[0]): return True return False class State: #NOTE: b = black, w = white #NOTE: P = pawn, R = rook, N = knight, B = bishop, Q = queen, K = king #NOTE w- and b- are placeholder can used for en passant piece_codes:tuple = ("--", "b-", "w-", "wP", "wR", "wN", "wB", "wQ", "wK", "bP", "bR", "bN", "bB", "bQ", "bK") blank:str = "--" def __init__(self, custom_board_flag=False, gamemode=GAMEMODE_OPTIONS[0], show_all_valid_moves_flag=False, timer_min=0, timer_sec=0, resolution=400, ): self.board = [["--" for i in range(8)] for j in range(8)] fg:bool = False if custom_board_flag: fg = self.load_board(True) if not fg: self.board = [ ["bR", "bN", "bB", "bK", "bQ", "bB", "bN", "bR"], ["bP", "bP", "bP", "bP", "bP", "bP", "bP", "bP"], ["--", "--", "--", "--", "--", "--", "--", "--"], ["--", "--", "--", "--", "--", "--", "--", "--"], ["--", "--", "--", "--", "--", "--", "--", "--"], ["--", "--", "--", "--", "--", "--", "--", "--"], ["wP", "wP", "wP", "wP", "wP", "wP", "wP", "wP"], ["wR", "wN", "wB", "wK", "wQ", "wB", "wN", "wR"] ] self.validate_board() self.white_move:bool = True self.move_log:list = [] self.moves:list = [] self._temp_moves:list = [] #from config file self.custom_board_flag = custom_board_flag self.gamemode = gamemode self.show_all_valid_moves_flag = show_all_valid_moves_flag self.timer_min = timer_min self.timer_sec = timer_sec self.resolution = resolution self.checkmate = False #! Asumes only 1 king of each colour on board self.white_king_pos = self.get_white_king_pos() # tuple of Position objs self.black_king_pos = self.get_black_king_pos() # tuple of Position objs #! this line MUST go last in init self.moves = self.get_valid_moves() # list of all possible moves (ex checks) for 1 move def load_board(self, flag:bool, filename=os.path.join("boards", "board.json")) -> bool: if flag: try: with open(filename, "r") as f: data = json.loads(f.read()) self.board = data return True except FileNotFoundError: print("failed to load custom board layout, using default") # return False except ValueError: print("board invalid, using default") # return False except IndexError: print("board invalid, using default") return False def validate_board(self)->None: for y in range(DIM): for x in range(DIM): if self.get_piece(Position(x,y,self)) not in self.piece_codes: raise ValueError(f"\"{self.get_piece(Position(x,y,self))}\" is not a valid piece code") @property def moves_ids(self)->list: return [self.moves[i].id for i in range(len(self.moves))] def get_white_king_pos(self)->tuple: arr:list = [] # for y_idx, y in enumerate(self.board): # for x_idx, x in enumerate(y): for y_idx in range(DIM): for x_idx in range(DIM): if self.get_piece(Position(x_idx, y_idx, self)) == "wK": return Position(x_idx, y_idx, self) def get_black_king_pos(self)->Position: pos:Position for y_idx, y in enumerate(self.board): for x_idx, x in enumerate(y): if self.get_piece(Position(x_idx, y_idx, self)) == "bK": return Position(x_idx, y_idx, self) def update_king_pos(self)->None: self.white_king_pos = self.get_white_king_pos() self.black_king_pos = self.get_black_king_pos() def undo_prev_move(self) -> None: if len(self.move_log) > 0: x:Move=self.move_log.pop() self.set_piece(x.start, x.start.piece_type) self.set_piece(x.end, x.end.piece_type) self.white_move = not self.white_move self.update_king_pos() # self.moves = self.get_valid_moves() def winner(self) -> str: if self.checkmate: return "White" if not self.white_move else "Black" # def get_piece(self, pos_obj: Position): # return self.board[pos_obj.y][pos_obj.x] get_piece = lambda self, pos_obj: self.board[pos_obj.y][pos_obj.x] def set_piece(self, pos_obj:Position, change_to:str) -> None: if pos_obj.piece_type in self.piece_codes: self.board[pos_obj.y][pos_obj.x] = change_to else: raise ValueError("set_piece given invalid piece type") # Unused #// def is_move_valid(self, move_obj:Move) -> bool: #// # check if player is trying to capture their own colour #// is_own_colour = lambda self, move_obj: False if move_obj.start.piece_type[0] == move_obj.end.piece_type[0] else True #// # for i in self.get_all_valid_moves_ex_checks(): #// # print(i) #// return is_own_colour(self, move_obj) and (move_obj in self.get_all_valid_moves_ex_checks()) def move_piece(self, move_obj:Move, arr:list) -> None: #// start, end = move_obj.start, move_obj.end #// piece_type, captured_type = self.get_piece(start), self.get_piece(end) if move_obj in arr: # print(move_obj) self.set_piece(move_obj.end, move_obj.start.piece_type) # replace enemy/black space with payers piece self.set_piece(move_obj.start, State.blank) # set moved piece to empty self.white_move = not self.white_move self.move_log.append(move_obj) self.update_king_pos() # self.moves = self.get_valid_moves() def move_piece_by_id(self, id)->None: x1,y1,x2,y2 = id self.move_piece(Move(Position(int(x1), int(y1), self), Position(int(x2), int(y2), self), self), self.moves) def get_valid_moves(self) -> list: copy_white_move = self.white_move self.moves.clear() # generate all possible moves arr:list=[] arr = self.get_all_valid_moves_ex_checks(arr) # appends moves to arr #! FIXED -------------------------------------------------------------------------------------------------- #// arr.append(Move(Position(0,0,self), Position(5,5,self), self)) # bad fix, the last elements is handled incorrectly # append random move to the end of the list # then pop it off on line the 2nd last line of the function #// arr.pop(); return arr # NOTE uncommenting this line removes the king in check check # NOTE also that when uncommented checkmate doesnt work and the game will only end when no piece can make a move #! FIXED -------------------------------------------------------------------------------------------------- # for each move make move self.white_move = not self.white_move for i in range(len(arr)-1, -1, -1): # looping backward as removing from list, prevents shifting indexes self.move_piece(arr[i], arr) # self.white_move = not self.white_move if self.is_checked(): # generates oppenents moves arr.remove(arr[i]) # remove invalid moves self.undo_prev_move() # undo temporary moves self.white_move = not copy_white_move # make sure white_move var is set correctly self.white_move = copy_white_move #// arr.pop() return arr def is_checked(self) -> bool: self.update_king_pos() if self.is_attacked_position(self.white_king_pos) or self.is_attacked_position(self.black_king_pos): return True #// else: #// if self.is_attacked_position(self.black_king_pos): #// return True #// self.white_move = not self.white_move return False def is_attacked_position(self, pos:Position)->bool: self.white_move = not self.white_move # to generate oppenents move opponents_moves = self.get_all_valid_moves_ex_checks(list()) self.white_move = not self.white_move # switch back op_mov:Move for op_mov in opponents_moves: #// print(op_mov.end) if op_mov.end == pos: return True return False def get_all_valid_moves_ex_checks(self, arr:list) -> None: #// move_arr:list = [] arr.clear() for y in range(DIM): for x in range(DIM): p = Position(x, y, self).piece_type piece_colour, piece_type = p #// print(piece_colour+piece_type) if (piece_colour == "w" and self.white_move) or (piece_colour == "b" and not self.white_move): # checking the Piece type and generating the correct move set for it match piece_type: # match statement added in python 3.10 case "-": # null char, used for en passand continue case "P": self.get_pawn_moves(x, y, arr) case "R": self.get_rook_moves(x, y, arr) case "N": self.get_knight_moves(x, y, arr) case "B": self.get_bishop_moves(x, y, arr) case "Q": self.get_queen_moves(x, y, arr) case "K": self.get_king_moves(x, y, arr) case default: # piece not valid raise ValueError(f"invalid piece: \"{piece_type}\"") return arr # helper function def generic_piece_move(self, x:int, y:int, matrix:tuple, arr:list, distance:int=7, no_jumps=True)->None: # var no_jumps is technically unused, but allow easy modification so im leaving it in distance+=1 # add to include the square the piece is on, to make more intuitive if not (0<distance<=8): # range check raise ValueError(f"distance is out of range and cant be {distance}") for i in matrix: if not isinstance(i, tuple): raise ValueError(f"{i} must be tuple") enemy_type:str = "b" if self.white_move else "w" m:tuple; i:int; endx:int; endy:int for m in matrix: for i in range(1, distance): endx, endy = x + m[0] * i, y + m[1] * i # perform vector move on piece and #increment vector magnitude based on piece max distance (normally 1 or 8) if 0<=endx<8 and 0<=endy<8: #range check target_piece = self.get_piece(Position(endx, endy, self)) if target_piece == State.blank: arr.append(Move(Position(x,y,self), Position(endx, endy,self), self)) elif target_piece[0]==enemy_type: arr.append(Move(Position(x,y,self), Position(endx, endy,self), self)) if no_jumps: break # prevent piece from jumping pieces else: # cant take white piece if no_jumps: break else: continue # // def get_pawn_moves(self, x:int, y:int): #//NOTE: have to do pawns taking on diagonals # // piece_infront: list = [] # // if self.white_move and Position(x, y ,self).piece_type == "wP": # // piece_infront.append(Position(x, y-1, self)) # // if y == 6: # // piece_infront.append(Position(x, y-2, self)) # // # if self.get_piece(piece_infront) == State.blank: # // # self.moves.append(Move(Position(x, y, self), piece_infront, self)) # // else: # // piece_infront.append(Position(x, y+1, self)) # // if y == 1: # // piece_infront.append(Position(x, y+2, self)) # // for i in piece_infront: # // if i.piece_type == State.blank: # // self.moves.append(Move(Position(x, y, self), i, self)) # // for i in self.moves: # // print(i) def get_pawn_moves(self, x:int, y:int, arr:list) -> None: # moves appended to arr # NOTE should refactor this code :) diagonals = ((1,1), (-1, 1), (1,-1), (-1,-1)) if self.white_move: try: if self.get_piece(Position(x,y-1, self)) == State.blank: arr.append(Move(Position(x,y,self), Position(x,y-1,self), self)) if self.get_piece(Position(x,y-2, self)) == State.blank and y == 6: arr.append(Move(Position(x,y,self), Position(x,y-2,self), self)) except IndexError: pass # check white diagonals for i in range(2,4): try: a,b = x + diagonals[i][0],y + diagonals[i][1] if a >= 0 and b >= 0: if self.get_piece(Position(a, b, self))[0] == "b": arr.append(Move(Position(x,y,self), Position(a, b, self), self)) except IndexError: pass else: try: if self.get_piece(Position(x,y+1, self)) == State.blank: arr.append(Move(Position(x,y,self), Position(x,y+1,self), self)) if self.get_piece(Position(x,y+2, self)) == State.blank and y == 1: arr.append(Move(Position(x,y,self), Position(x,y+2,self), self)) except IndexError: pass # checks blacks diagonals for i in range(2): try: a,b = x + diagonals[i][0],y + diagonals[i][1] if a >= 0 and b >= 0: if self.get_piece(Position(a, b, self))[0] == "w": arr.append(Move(Position(x,y,self), Position(a, b, self), self)) except IndexError: pass def get_rook_moves(self, x:int, y:int, arr:list) -> None: # moves appended to arr matrix = ((1, 0), (-1, 0), (0, 1), (0, -1)) self.generic_piece_move(x, y, matrix, arr) def get_bishop_moves(self, x:int, y:int, arr:list) -> None: # moves appended to arr matrix:tuple = ((1, 1), (-1, -1), (-1, 1), (1, -1)) self.generic_piece_move(x, y, matrix, arr) def get_knight_moves(self, x:int, y:int, arr:list) -> None: # moves appended to arr matrix = ((2,1), (2,-1), (-2,1), (-2,-1), (1,-2), (1,2), (-1, -2), (-1, 2)) self.generic_piece_move(x,y,matrix,arr, distance=1, no_jumps=False) #NOTE: no_jumps doesnt change to behavour in this instance as # it only has a dist of 1 so technically makes no jumps in the # code, due to the matrix moving the knight by +-2 and +-1 in # the x or y. # # a "jump" is defined by the number of times the matrix # is applied to the piece def get_king_moves(self, x:int, y:int, arr:list) -> None: # moves appended to arr iterations = [1, 0, -1] test = list(itertools.permutations(iterations, 2)) test.append((1,1)); test.append((-1,-1)) self.generic_piece_move(x, y, tuple(test), arr, distance=1) def get_queen_moves(self, x:int, y:int, arr:list) -> None: # moves appended to arr iterations = [1, 0, -1] test = list(itertools.permutations(iterations, 2)) # gets all permutations but not repeats e.g. (1,1) etc test.append((1,1)); test.append((-1,-1)) # add missed permutations self.generic_piece_move(x, y, tuple(test), arr)
Editor is loading...
Leave a Comment