online multiplayer chess game (note server currently down)
Diffstat (limited to 'ui/board/Board.gd')
| -rw-r--r-- | ui/board/Board.gd | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/ui/board/Board.gd b/ui/board/Board.gd new file mode 100644 index 0000000..eb71113 --- /dev/null +++ b/ui/board/Board.gd @@ -0,0 +1,315 @@ +extends Control +class_name Grid + +const PieceScene := preload("res://piece/Piece.tscn") +const Square := preload("res://Square.tscn") + +### for the sandisplay +signal add_to_pgn(move) +signal clear_pgn +signal load_pgn(moves) +signal remove_last + +const piece_size := Vector2(80, 80) + +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 board = [] # has `get_piece(algebraic position)` and `set_piece(algebraic position)` for ease of use + + +func get_piece(alg: String) -> Piece: + return board[Chess.SQUARE_MAP[alg]] + + +func set_piece(alg: String, p: Piece) -> void: + board[Chess.SQUARE_MAP[alg]] = p + + +var flipped = false +var labels = {numbers = [], letters = []} +var background_array = [] +var last_clicked: Piece = null +var last_clicked_moves := [] + +export(NodePath) var sidebar_path = @"" +onready var sidebar := get_node_or_null(sidebar_path) +export(NodePath) var ui_path = @"" +onready var darken = $Darken +onready var ui := get_node_or_null(ui_path) +onready var foreground := $Foreground +onready var background := $Background +onready var pieces := $Pieces + +var chess := Chess.new() + + +func _init(): + Globals.grid = self + + +func _exit_tree(): + Globals.grid = null + + +func _ready(): + Events.connect("turn_over", self, "_on_turn_over") + PacketHandler.connect("move_data", self, "move") + rect_min_size = piece_size * 8 + rect_pivot_offset = rect_min_size / 2 + board.resize(128) + init_board() + init_labels() + create_pieces() + + +func init_board() -> void: # create the board + background_array.resize(128) + for i in Chess.SQUARE_MAP.values(): + var alg = Chess.algebraic(i) + var square := Square.instance() # create a square + square.name = alg + square.hint_tooltip = alg + square.color = Globals.board_color1 if Chess.square_color(alg) == "light" else Globals.board_color2 # set the color + background.add_child(square) # add the square to the background + square.connect("clicked", self, "square_clicked", [alg]) # connect the clicked event + background_array[i] = square # add the square to the background array + $Arrows._setup(self) # initialize the arrows + + +func init_labels() -> void: + foreground.offset = rect_global_position + for i in range(8): + labels.letters.append(init_label(i, Vector2(i, 7), "abcdefgh"[i], Vector2(10, -10), Label.VALIGN_BOTTOM)) + labels.numbers.append(init_label(i, Vector2(7, i), str(8 - i), Vector2(-10, 10), 0, Label.VALIGN_BOTTOM)) + + +func init_label(i: int, position: Vector2, text: String, off := Vector2.ZERO, valign := 0, align := 0) -> 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/ubuntu-bold.tres").duplicate() + font.size = 15 + label.add_font_override("font", font) + foreground.add_child(label) + return label + + +func create_pieces(): + for k in Chess.SQUARE_MAP: + var piece = chess.get(k) + if piece: + make_piece(k, piece.type, piece.color) + + +func make_piece(algebraic: String, piece_type: String, color := "w") -> void: # make peace + var piece := PieceScene.instance() # create a piece + var position = Chess.algebraic2vec(algebraic) # get the position + piece.name = "%s@%s" % [piece_type, algebraic] + piece.position = algebraic + piece.type = piece_type + piece.rect_global_position = position * piece_size # set the global position + piece.rect_min_size = piece_size + piece.rect_pivot_offset = piece_size / 2 # rotate around center + piece.color = color + pieces.add_child(piece) # add the piece to the grid + set_piece(algebraic, piece) + + +func flip_pieces() -> void: + for i in Chess.SQUARE_MAP.values(): + var spot: Piece = board[i] + 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] + var letlabel: Label = labels.letters[i] + var number := i + 1 if flipped else 8 - i + numlabel.text = str(number) + letlabel.text = "hgfedcba"[number - 1] + + +func flip_board() -> void: + sidebar.flip_panels() + if flipped: + flipped = false + rect_rotation = 0 + flip_pieces() + flip_labels() + else: + flipped = true + rect_rotation = 180 + flip_pieces() + flip_labels() + + +func square_clicked(clicked_square: String) -> void: + if chess.turn != Globals.team or Globals.spectating: + return + var p := get_piece(clicked_square) + if !p or p.color != Globals.team: + if !is_instance_valid(last_clicked): + return + for m in last_clicked_moves: + if m.to == clicked_square && m.from == last_clicked.position: + move(m.san, false) + break + clear_circles() + + elif last_clicked != p: + if is_instance_valid(last_clicked): + clear_circles() + last_clicked = p + p.background.show() + var movs = chess.moves({"square": clicked_square, "verbose": true}) + for mov in movs: + if "c" in mov.flags: + get_piece(mov.to).frame.show() + else: + background_array[Chess.SQUARE_MAP[mov.to]].circle.show() + #e.p && castling dont really need attention here + last_clicked_moves.append(mov) + + +func move(san: String, is_recieved_move := true) -> void: + if is_valid_move(san): + var sound_handled = false + var move_0x88 = chess.__move_from_san(san, true) + chess.__make_move(move_0x88) + if move_0x88.flags & Chess.BITS.CAPTURE: + board[move_0x88.to].took() + SoundFx.play("Capture") + sound_handled = true + elif move_0x88.flags & Chess.BITS.EP_CAPTURE: + var to_take := Chess.offset(move_0x88.to, Vector2(0, 1 * -1 if chess.turn == Chess.WHITE else 1)) + get_piece(to_take).took() + SoundFx.play("Capture") + sound_handled = true + elif move_0x88.flags & Chess.BITS.KSIDE_CASTLE: # kingside castling + var rook_pos := Chess.offset(move_0x88.to, Vector2(1, 0)) + var _rook := get_piece(rook_pos).move(Chess.offset(move_0x88.to, Vector2(-1, 0))) + elif move_0x88.flags & Chess.BITS.QSIDE_CASTLE: # queenside + var rook_pos := Chess.offset(move_0x88.to, Vector2(-2, 0)) + var _rook := get_piece(rook_pos).move(Chess.offset(move_0x88.to, Vector2(1, 0))) + if move_0x88.flags & Chess.BITS.PROMOTION: #promotion wow + var p: Piece = board[move_0x88.from].move(Chess.algebraic(move_0x88.to)) + if !is_recieved_move: # was my turn, this is my move + yield(p.tween, "tween_all_completed") + p.open_promotion_previews() + yield(p, "promotion_decided") + move_0x88["promotion"] = p.promote_to + san = chess.__move_to_san(move_0x88) # update the san with new promotion data + p.queue_free() + else: # was opponents turn, this is opponents move: promotion is already chosen + p.queue_free() # the q_f above happens after a dozen yields + # the move animation is useless if its not my turn + # but it changes p.position, so its usefull. + make_piece(p.position, move_0x88.promotion, p.color) + SoundFx.play("Move" if move_0x88.flags & Chess.BITS.NORMAL else "Capture") + sound_handled = true + else: # not promotion: from **always** moves to `to` + var _p = board[move_0x88.from].move(Chess.algebraic(move_0x88.to)) + if !is_recieved_move: + PacketHandler.send_mov(san) + if !sound_handled: + SoundFx.play("Move") + emit_signal("add_to_pgn", san) + Events.emit_signal("turn_over") + else: + Log.err("move %s is invalid!" % san) + + +func is_valid_move(san: String) -> bool: + var movs = chess.moves() + for mov in movs: + if mov == san: + return true + return false + + +func clear_circles(): + darken.hide() + if not last_clicked: + return + last_clicked.background.hide() + for move in last_clicked_moves: + if ("c" in move.flags or "e" in move.flags) and get_piece(move.to): # the take may have been used as the move, so this may just do nothing. on enpasant + get_piece(move.to).frame.hide() # for the take circle + background_array[Chess.SQUARE_MAP[move.to]].circle.hide() + last_clicked_moves = [] + last_clicked = null + + +func clear_pieces() -> void: + for i in Chess.SQUARE_MAP.values(): + var p = board[i] + if p: + p.queue_free() + board[i] = null + + +func draw(reason := "") -> void: + var string = "draw by " + reason + ui.set_status(string, 0) + Events.emit_signal("game_over", string, true) + SoundFx.play("Victory") + yield(get_tree().create_timer(5), "timeout") + Events.emit_signal("go_back", string, true) + + +func win(winner: String, reason := "") -> void: + var string = "%s won the game by %s" % [Utils.expand_color(winner), reason] + ui.set_status(string, 0) #: black won the game by checkmate + Events.emit_signal("game_over", string, true) + SoundFx.play("Victory") + yield(get_tree().create_timer(5), "timeout") + Events.emit_signal("go_back", string, true) + + +func load_pgn(pgn: String) -> void: + chess.load_pgn(pgn, {sloppy = true}) + clear_pieces() + create_pieces() + emit_signal("clear_pgn") + var movs: PoolStringArray = Pgn.parse(pgn).moves + emit_signal("load_pgn", movs) + check_game_over() + + +func undo(two: bool = false) -> void: + Globals.chat.server("undid move %s" % chess.undo().san) + emit_signal("remove_last") + if two: + Globals.chat.server("undid move %s" % chess.undo().san) + emit_signal("remove_last") + clear_pieces() + clear_circles() + create_pieces() + + +func _on_turn_over(): + check_game_over() + + +func check_game_over(): + if chess.in_checkmate(): + # they won if its my turn, i won if its their turn. + win(Globals.team if Globals.team != chess.turn else Chess.__swap_color(Globals.team), "checkmate") + elif chess.half_moves >= 50: + draw("fifty move rule") + elif chess.in_stalemate(): + draw("stalemate") + elif chess.insufficient_material(): + draw("insufficient material") + elif chess.in_threefold_repetition(): + draw("threefold repetition") |