online multiplayer chess game (note server currently down)
Diffstat (limited to 'Board.gd')
| -rw-r--r-- | Board.gd | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/Board.gd b/Board.gd new file mode 100644 index 0000000..0f2fba5 --- /dev/null +++ b/Board.gd @@ -0,0 +1,470 @@ +extends Control +class_name Grid + +const PieceScene := preload("res://Piece.tscn") +const Square := preload("res://Square.tscn") + +const piece_size := Vector2(80, 80) +const default_metadata := { + "wccl": false, # white can castle left + "wccr": false, # white can castle right + "bccl": false, # black can castle left + "bccr": false, # black can castle right + "turn": true, # true = white, false = black + "wcep": [], # white can enpassant + "bcep": [], # black can enpassant +} + +onready var offset = rect_position + +export(Color) var overlay_color := Color(0.078431, 0.333333, 0.117647, 0.498039) +export(Color) var clockrunning_color := Color(0.219608, 0.278431, 0.133333) +export(Color) var clockrunninglow := Color(0.47451, 0.172549, 0.164706) +export(Color) var clocklow := Color(0.313726, 0.156863, 0.14902) + +var matrix := [] +var stop_input := false +var background_matrix := [] +var history_matrixes := {} +var last_clicked: Piece = null +var flipped := false + +var labels := {"letters": [], "numbers": []} + +onready var background := $Background +onready var ASSETS_PATH: String = "res://assets/pieces/%s/" % Globals.piece_set +onready var foreground := $Foreground +onready var pieces := $Pieces +export(NodePath) onready var status = get_node(status) as StatusLabel + + +func _init() -> void: + Globals.grid = self + + +func _ready() -> void: + if Globals.network: + Globals.network.connect("move_data", self, "play_san") + init_board() # create the tile squares + init_matrix() # create the pieces + init_labels() # add the labels + Events.connect("turn_over", self, "_on_turn_over") # listen for turn_over events + Events.connect("outoftime", self, "_on_outoftime") # listen for timeout events + + Debug.monitor(self, "last_clicked") + Debug.monitor(self, "matrix", "matrix[8]") + Debug.monitor(self, "highest value in 3fold", "threefoldrepetition()") + Debug.monitor(self, "stop_input") + + +func _exit_tree() -> void: + Globals.grid = null # reset the globals grid when leaving tree + + +func _input(event: InputEvent) -> void: # input + if event.is_action_released("debug"): # if debug + print_matrix_pretty(matrix) # print the matrix + + +static func print_matrix_pretty(mat: Array) -> void: # print the matrix + var topper_header := "┏━━━┳━━━┳━━━┳━━━┳━━━┳━━━┳━━━┳━━━┳━━━┓" + var middle_header := "┣━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━┫" + var middish_heads := "┗━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━┫" + var smaller_heads := " ┗━━━┻━━━┻━━━┻━━━┻━━━┻━━━┻━━━┻━━━┛" + var letter_header := " ┃ a ┃ b ┃ c ┃ d ┃ e ┃ f ┃ g ┃ h ┃" + var ender := " ┃ " # for pretty prints + for j in range(8): # for each row + var r: Array = mat[j] # get the row + if j == 0: + print(topper_header) # print the top border + else: + print(middle_header) # print the middle border + var row := "%s %s%s" % [ender.strip_edges(), 8 - j, ender] # init the string + for i in range(8): # for each column + var c: Piece = r[i] # get the column + row += "%s%s" % [c.mininame, ender] if c else " " + ender # add the piece + print(row) # print the string + print("%s\n%s\n%s" % [middish_heads, letter_header, smaller_heads]) + + +func reload_sprites() -> void: + for i in range(8): + for j in range(8): + if matrix[i][j]: + matrix[i][j].load_texture() + + +func flip_pieces() -> void: + for i in range(8): + for j in range(8): + var spot: Piece = matrix[i][j] + if spot: + spot.sprite.flip_v = flipped + spot.sprite.flip_h = flipped + + +func flip_labels() -> void: + for i in range(8): + var numlabel: Label = labels.numbers[i].get_node("Label") + var letlabel: Label = labels.letters[i].get_node("Label") + var number := i + 1 if flipped else 8 - i + numlabel.text = str(number) + letlabel.text = "hgfedcba"[number - 1] + + +func flip_board() -> void: + if flipped: + flipped = false + rect_rotation = 0 + flip_pieces() + flip_labels() + else: + flipped = true + rect_rotation = 180 + flip_pieces() + flip_labels() + + +func init_labels() -> void: + for i in range(8): + labels.letters.append( + init_label( + i, + Vector2(i, 7), + "abcdefgh"[i], + Label.VALIGN_BOTTOM, + Label.ALIGN_LEFT, + (Vector2.RIGHT + Vector2.UP) * 10 + ) + ) + labels.numbers.append( + init_label( + i, + Vector2(7, i), + str(8 - i), + Label.VALIGN_TOP, + Label.ALIGN_RIGHT, + (Vector2.LEFT + Vector2.DOWN) * 10 + ) + ) + + +func init_label(i: int, position: Vector2, text: String, valign := 0, align := 0, off := Vector2.ZERO) -> Label: + var label := Label.new() + label.rect_size = piece_size + label.align = align + label.valign = valign + label.rect_position = (position * piece_size) + off + label.text = text + label.add_color_override("font_color", Globals.board_color1 if i % 2 == 0 else Globals.board_color2) + var font: DynamicFont = load("res://ui/verdana-bold.tres") + font.size = 15 + label.add_font_override("font", font) + foreground.add_child(label) + return label + + +func threefoldrepetition() -> int: + return 0 if !history_matrixes.values() else history_matrixes.values().max() + + +func mat2str(mat: Array = matrix) -> String: + var string := "" + for y in range(8): + for x in range(8): + var spot: Piece = mat[y][x] + string += spot.mininame if spot else "*" + for key in mat[8].keys(): # store the metadata + string += "%s:%s" % [key, mat[8][key]] + return string + + +func drawed(reason := "") -> void: + status.set_text("draw by " + reason, 0) + Events.emit_signal("game_over") + SoundFx.play("Draw") + yield(get_tree().create_timer(5), "timeout") + Events.emit_signal("go_back") + + +func win(winner: bool, reason := "") -> void: + status.set_text("%s won the game by %s" % ["white" if winner else "black", reason], 0) # black won the game by checkmate + Events.emit_signal("game_over") + Log.info("%s won the game in %s turns!" % ["white" if winner else "black", Globals.fullmove]) + SoundFx.play("Victory") + yield(get_tree().create_timer(5), "timeout") + Events.emit_signal("go_back") + + +func check_in_check(prin := false) -> bool: # check if in_check + for i in range(0, 8): # for each row + for j in range(0, 8): # for each column + var spot: Piece = matrix[i][j] # get the square + if spot and spot.white != Globals.turn: # enemie + if spot.can_attack_piece(Globals.white_king if Globals.turn else Globals.black_king): # if it can take the king + if prin: + # control never flows here + Globals.in_check = true # set in_check + Globals.checking_piece = spot # set checking_piece + SoundFx.play("Check") + return true # stop at the first check found + return false + + +func can_move() -> bool: + for i in range(0, 8): # for each row + for j in range(0, 8): # for each column + var spot: Piece = matrix[i][j] # get the square + if spot and spot.white != Globals.team: # enemie: checking for our enemys + if spot.can_move(): + return true + return false + + +func init_matrix() -> void: # create the matrix + for i in range(8): # for each row + matrix.append([]) # add a row + for _j in range(8): # for each column + matrix[i].append(null) # add a square + matrix.append(default_metadata.duplicate()) # metadata for threefold repetition check + add_pieces() # add the pieces + + +func make_piece(position: Vector2, script: String, white: bool = true) -> void: # make peace + var piece := PieceScene.instance() # create a piece + piece.script = load("res://pieces/%s.gd" % script) # set the script + piece.real_position = position # set the real position + piece.rect_global_position = position * piece_size # set the global position + piece.white = white # set its team + pieces.add_child(piece) # add the piece to the grid + matrix[position.y][position.x] = piece + + +func init_board() -> void: # create the board + for i in range(8): # for each row + background_matrix.append([]) # add a row + for j in range(8): # for each column + var square := Square.instance() # create a square + square.rect_size = piece_size # set the size + square.rect_global_position = Vector2(i, j) * piece_size # set the position + square.color = Globals.board_color1 if (i + j) % 2 == 0 else Globals.board_color2 # set the color + background.add_child(square) # add the square to the background + square.connect("clicked", self, "square_clicked", [Vector2(i, j)]) # connect the clicked event + background_matrix[i].append(square) # add the square to the background matrix + + +func add_pieces() -> void: # add the pieces + add_pawns() + add_rooks() + add_knights() + add_bishops() + add_queens() + add_kings() + + +func add_pawns() -> void: + for i in range(8): + make_piece(Vector2(i, 1), "Pawn", false) + make_piece(Vector2(i, 6), "Pawn", true) + + +func add_rooks() -> void: + make_piece(Vector2(0, 0), "Rook", false) + make_piece(Vector2(7, 0), "Rook", false) + make_piece(Vector2(0, 7), "Rook", true) + make_piece(Vector2(7, 7), "Rook", true) + + +func add_knights() -> void: + make_piece(Vector2(1, 0), "Knight", false) + make_piece(Vector2(6, 0), "Knight", false) + make_piece(Vector2(1, 7), "Knight", true) + make_piece(Vector2(6, 7), "Knight", true) + + +func add_bishops() -> void: + make_piece(Vector2(2, 0), "Bishop", false) + make_piece(Vector2(5, 0), "Bishop", false) + make_piece(Vector2(2, 7), "Bishop", true) + make_piece(Vector2(5, 7), "Bishop", true) + + +func add_queens() -> void: + make_piece(Vector2(3, 0), "Queen", false) + make_piece(Vector2(3, 7), "Queen", true) + + +func add_kings() -> void: + make_piece(Vector2(4, 0), "King", false) + make_piece(Vector2(4, 7), "King", true) + Globals.white_king = matrix[7][4] # set the white king + Globals.black_king = matrix[0][4] # set the black king + + +func check_for_circle(position: Vector2) -> bool: # check for a circle, validating movement + return background_matrix[position.x][position.y].circle_on + + +func check_for_frame(position: Vector2) -> bool: # check for a frame, validating taking + if !is_instance_valid(matrix[position.y][position.x]): # if there is no piece + return false # there is no frame + return matrix[position.y][position.x].frameon # return if the frame is on + + +func square_clicked(position: Vector2) -> void: # square clicked + if stop_input: + return + if Globals.turn != Globals.team: + return + Log.debug(Utils.to_algebraic(position) + " clicked") + var spot: Piece = matrix[position.y][position.x] # get the spot + if !spot or spot.white != Globals.team: + if !is_instance_valid(last_clicked): + return + if check_for_frame(position): # takeable + handle_take(position) + stop_input = true + elif check_for_circle(position): # see if theres a circle at the position + handle_move(position) # move + stop_input = true + last_clicked.clear_clicked() # remove the circles + last_clicked = null # set it to null + elif last_clicked != spot: # we got a new piece (or pawn) clicked + if is_instance_valid(last_clicked): # remove the circles + last_clicked.clear_clicked() + last_clicked = spot # set it to the new spot + spot.clicked() # tell the piece shit happeend + + +func handle_take(position: Vector2) -> void: + if Utils.is_pawn(last_clicked): # if its a pawn + if check_promote(last_clicked, position, "take"): + return + var mov = Move.new(SanParse.from_str(last_clicked.shortname), [last_clicked.real_position, position], true) + Globals.network.send_mov(mov) # piece taking piece + + +func handle_move(position: Vector2) -> void: + if Utils.is_king(last_clicked) and last_clicked.can_castle: + for i in range(len(last_clicked.can_castle)): + var castle_data = last_clicked.can_castle[i] + if castle_data[0] == position: + # send some packet + var mov = Move.new(SanParser.KING, Move.castle_type(castle_data[3])) + Globals.network.send_mov(mov) + return + if Utils.is_pawn(last_clicked): + var pawn: Pawn = last_clicked + if pawn.enpassant: + for i in range(len(pawn.enpassant)): + var en_passant_data = pawn.enpassant[i] + if en_passant_data[0] == position: + # send some packet + var mov = Move.new(SanParser.PAWN, [pawn.real_position, position], true) + Globals.network.send_mov(mov) + return + elif check_promote(pawn, position): + return + var mov = Move.new(SanParse.from_str(last_clicked.shortname), [last_clicked.real_position, position]) + Globals.network.send_mov(mov) + + +func check_promote(pawn, position, calltype: String = "move") -> bool: + if pawn.can_promote(position): + pawn.promote(position, calltype) + return true + return false + + +func clear_fx() -> void: # clear the circles + for i in range(8): # for each row + for j in range(8): # for each column + var square: ColorRect = background_matrix[i][j] # get the square + square.set_circle(false) # set the circle to false + var piece: Piece = matrix[i][j] # get the piece + if piece: # if there is a piece + piece.set_frame(false) # clear the frame + + +func _on_outoftime(who: bool) -> void: + win(who, "time") + + +func _on_turn_over() -> void: + var matstr := mat2str() + # Log.debug("matstr: " + matstr) + if !matstr in history_matrixes: + # Log.debug("new matrix entry") + history_matrixes[matstr] = 1 + else: + # Log.debug(["matrix entry = ", history_matrixes[matstr], "+ 1"]) + history_matrixes[matstr] += 1 + Globals.checking_piece = null # reset checking_piece + Globals.in_check = false # reset in_check + matrix[8] = default_metadata.duplicate() # add the metadata to the matrix + matrix[8].turn = Globals.turn + check_in_check(true) # check if in_check + if !can_move(): + if Globals.in_check: + win(!Globals.turn, "checkmate") + else: + drawed("stalemate") + elif threefoldrepetition() >= 3: + drawed("threefold repetition") + + +func play_pgn(pgn: String, instant := false): + stop_input = true + for san in Pgn.parse(pgn).moves: + play_san(san, false, false) # instant is not working right right now + # so just change the delay :> + if instant: + yield(get_tree(), "idle_frame") + else: + yield(get_tree().create_timer(.3), "timeout") + stop_input = false + + +func play_san(san: String, instant := false, set_input := true) -> void: + Log.debug("playing " + san) + var san_to_add := san + var mov = SanParse.parse(san) + mov.make_long() + Globals.add_turn() + match mov.move_kind.type: + Move.MoveKind.CASTLE: + var side = 0 if Globals.turn else 7 + var rook: Rook + var rook_goto: Vector2 + var kingpos = Vector2(4, side) + var king: King = Piece.at_pos(kingpos) + var king_goto: Vector2 + match mov.move_kind.data: + Move.MoveKind.CASTLETYPES.KING_SIDE: + rook = Piece.at_pos(Vector2(7, side)) + rook_goto = Vector2(5, side) + king_goto = Vector2(6, side) + Move.MoveKind.CASTLETYPES.QUEEN_SIDE: + rook = Piece.at_pos(Vector2(0, side)) + rook_goto = Vector2(3, side) + king_goto = Vector2(2, side) + rook.moveto(rook_goto, instant) + king.castle(king_goto, instant) + Move.MoveKind.NORMAL: + # this handles promotion, taking, enpassant, and moves. + var positions = mov.move_kind.data + if mov.promotion != -1: # promotion part + var promote_to = Utils.to_str(mov.promotion) + Piece.at_pos(positions[0]).promote_to(promote_to, mov.is_capture, positions[1], instant) + + elif mov.is_capture: # taking part + if Piece.at_pos(positions[1]): + Piece.at_pos(positions[0]).take(Piece.at_pos(positions[1]), instant) + elif mov.piece == SanParser.PAWN: # enpassant part + var pawn: Pawn = Piece.at_pos(positions[0]) + pawn.passant(positions[1], instant) + san_to_add += " e.p." + else: # a very normal move + Piece.at_pos(positions[0]).moveto(positions[1], instant) + Utils.add_move(san_to_add) + stop_input = false if set_input else stop_input |