online multiplayer chess game (note server currently down)
UNDOS
- workflow update?
- fen support
- threefold repetition DITCHED
37 files changed, 704 insertions, 417 deletions
diff --git a/.github/workflows/export.yml b/.github/workflows/export.yml index f0cdba4..7b912b9 100644 --- a/.github/workflows/export.yml +++ b/.github/workflows/export.yml @@ -1,26 +1,110 @@ +name: "export" on: push: branches: - main +env: + GODOT_VERSION: 3.4.4 + EXPORT_NAME: chess + jobs: - export_game: - permissions: write-all # can be read-all, write-all, or read-write - # Always use ubuntu-latest for this action - runs-on: ubuntu-latest - name: export + export-windows: + name: Windows Export + runs-on: ubuntu-20.04 + container: + image: docker://barichello/godot-ci:3.4.4 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + lfs: true + - name: Setup + run: | + mkdir -v -p ~/.local/share/godot/templates + mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable + - name: Windows Build + run: | + mkdir -v -p build/windows + godot -v --export "Windows" ./build/windows/$EXPORT_NAME.exe + - name: Upload Artifact + uses: actions/upload-artifact@v1 + with: + name: windows + path: build/windows + + export-linux: + name: Linux Export + runs-on: ubuntu-20.04 + container: + image: docker://barichello/godot-ci:3.4.4 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup + run: | + mkdir -v -p ~/.local/share/godot/templates + mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable + - name: Linux Build + run: | + mkdir -v -p build/linux + godot -v --export "Linux" ./build/linux/$EXPORT_NAME.x86_64 + - name: Upload Artifact + uses: actions/upload-artifact@v1 + with: + name: linux + path: build/linux + + export-web: + name: Web Export + runs-on: ubuntu-20.04 + container: + image: docker://barichello/godot-ci:3.4.4 steps: - - name: checkout - uses: actions/[email protected] - with: # Ensure that you get the entire project history - fetch-depth: 0 + - name: Checkout + uses: actions/checkout@v2 + - name: Setup + run: | + mkdir -v -p ~/.local/share/godot/templates + mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable + - name: Web Build + run: | + mkdir -v -p build/web + godot -v --export "HTML" ./build/web/index.html + - name: Upload Artifact + uses: actions/upload-artifact@v1 + with: + name: web + path: build/web + - name: Install rsync 📚 + run: | + apt-get update && apt-get install -y rsync + - name: Deploy to GitHub Pages 🚀 + uses: JamesIves/github-pages-deploy-action@releases/v4 + with: + branch: gh-pages # The branch the action should deploy to. + folder: build/web # The folder the action should deploy. - - name: export game - uses: firebelley/[email protected] + export-mac: + name: Mac Export + runs-on: ubuntu-20.04 + container: + image: docker://barichello/godot-ci:3.4.4 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + lfs: true + - name: Setup + run: | + mkdir -v -p ~/.local/share/godot/templates + mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable + - name: Mac Build + run: | + mkdir -v -p build/mac + godot -v --export "Mac" ./build/mac/$EXPORT_NAME.zip + - name: Upload Artifact + uses: actions/upload-artifact@v1 with: - # Defining all the required inputs - godot_executable_download_url: https://downloads.tuxfamily.org/godotengine/3.4.4/Godot_v3.4.4-stable_linux_headless.64.zip - godot_export_templates_download_url: https://downloads.tuxfamily.org/godotengine/3.4.4/Godot_v3.4.4-stable_export_templates.tpz - relative_project_path: ./ - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: mac + path: build/mac @@ -5,15 +5,6 @@ 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 -} 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) @@ -23,10 +14,11 @@ export(Color) var clocklow := Color(0.313726, 0.156863, 0.14902) var matrix := [] var stop_input := true var background_array := [] -var history_matrixes := {} var last_clicked: Piece = null var flipped := false +signal move_decided + var labels := {letters = [], numbers = []} onready var background := $Background @@ -36,6 +28,8 @@ onready var pieces := $Pieces export(NodePath) onready var ui = get_node(ui) export(NodePath) onready var sidebar = get_node(sidebar) +enum { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING } + func _init() -> void: Globals.grid = self @@ -46,14 +40,13 @@ 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_matrix() # create 2d matrix + init_pieces() # 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, "meta", "matrix[8]") - Debug.monitor(self, "highest value in 3fold", "threefoldrepetition()") stop_input = false @@ -81,8 +74,11 @@ static func print_matrix_pretty(mat: Array) -> void: # print the matrix 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 + if typeof(r[i]) != TYPE_STRING: + var c: Piece = r[i] # get the column + row += "%s%s" % [c.mininame, ender] if c else " " + ender # add the piece + else: + row += "%s%s" % [r[i] if r[i] else " ", ender] print(row) # print the string print("%s\n%s\n%s" % [middish_heads, letter_header, smaller_heads]) @@ -134,21 +130,34 @@ 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], Label.VALIGN_BOTTOM, Label.ALIGN_LEFT, Vector2(10, -10)) + init_label( + i, + Vector2(i, 7), + "abcdefgh"[i], + Label.VALIGN_BOTTOM, + Label.ALIGN_LEFT, + Vector2(10, -10) + ) ) labels.numbers.append( - init_label(i, Vector2(7, i), str(8 - i), Label.VALIGN_TOP, Label.ALIGN_RIGHT, Vector2(-10, 10)) + init_label( + i, Vector2(7, i), str(8 - i), Label.VALIGN_TOP, Label.ALIGN_RIGHT, Vector2(-10, 10) + ) ) -func init_label(i: int, position: Vector2, text: String, valign := 0, align := 0, off := Vector2.ZERO) -> Label: +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) + 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").duplicate() font.size = 15 label.add_font_override("font", font) @@ -156,21 +165,6 @@ func init_label(i: int, position: Vector2, text: String, valign := 0, align := 0 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: ui.set_status("draw by " + reason, 0) Events.emit_signal("game_over") @@ -193,7 +187,9 @@ func check_in_check(prin := false) -> bool: # check if in_check 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 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 @@ -218,14 +214,16 @@ func init_matrix() -> void: # create the matrix 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 +func make_piece( + position: Vector2, piece_type: int, white: bool = true, visible: bool = true +) -> void: # make peace var piece := PieceScene.instance() # create a piece - piece.script = load("res://pieces/%s.gd" % script) # set the script + piece.name = Utils.to_str(piece_type) + piece.script = load("res://pieces/%s.gd" % Utils.to_str(piece_type)) # set the script piece.real_position = position # set the real position + piece.visible = visible 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 @@ -249,52 +247,8 @@ func get_background_element(pos: Vector2) -> ColorRect: return background_array[8 * pos.y + pos.x] -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 init_pieces(visible: bool = true) -> void: # add the pieces + load_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", visible) func check_for_circle(position: Vector2) -> bool: # check for a circle, validating movement @@ -320,9 +274,11 @@ func square_clicked(position: Vector2) -> void: # square clicked if check_for_frame(position): # takeable handle_take(position) stop_input = true + emit_signal("move_decided") elif check_for_circle(position): # see if theres a circle at the position handle_move(position) # move stop_input = true + emit_signal("move_decided") if last_clicked: last_clicked.clear_clicked() # remove the circles last_clicked = null # set it to null @@ -337,7 +293,9 @@ 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) + var mov = Move.new( + SanParse.from_str(last_clicked.shortname), [last_clicked.real_position, position], true + ) Globals.network.send_mov(mov) # piece taking piece @@ -347,7 +305,7 @@ func handle_move(position: Vector2) -> void: 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])) + var mov = Move.new(SanParser.KING, castle_data[3]) Globals.network.send_mov(mov) return if Utils.is_pawn(last_clicked): @@ -362,7 +320,9 @@ func handle_move(position: Vector2) -> void: return elif check_promote(pawn, position): return - var mov = Move.new(SanParse.from_str(last_clicked.shortname), [last_clicked.real_position, position]) + var mov = Move.new( + SanParse.from_str(last_clicked.shortname), [last_clicked.real_position, position] + ) Globals.network.send_mov(mov) @@ -388,45 +348,48 @@ func _on_outoftime(who: bool) -> void: 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): + var hitlist = [] + if instant: + kill_matrix() + hitlist = pieces.get_children() + else: + kill_pieces() stop_input = true - for san in Pgn.parse(pgn).moves: - play_san(san, false, false) # instant is not working right right now + init_pieces(!instant) # if instant, hide the pieces + var mvtext = Pgn.parse(pgn).moves + for san in mvtext: + play_san(san, instant) # 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") + for i in range(8): + for j in range(8): + if matrix[i][j]: + matrix[i][j].update_visual_position() + matrix[i][j].show() + for c in hitlist: + if is_instance_valid(c): + c.free() 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() + var mov = SanParse.parse(san).make_long() Globals.add_turn() match mov.move_kind.type: Move.MoveKind.CASTLE: @@ -451,10 +414,12 @@ func play_san(san: String, instant := false, set_input := true) -> void: # 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) + Piece.at_pos(positions[0]).promote_to( + mov.promotion, mov.is_capture, positions[1], instant + ) elif mov.is_capture: # taking part + Globals.reset_halfmove() 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 @@ -462,6 +427,55 @@ func play_san(san: String, instant := false, set_input := true) -> void: pawn.passant(positions[1], instant) san_to_add += " e.p." else: # a very normal move - Piece.at_pos(positions[0]).moveto(positions[1], instant) + var piece = Piece.at_pos(positions[0]) + piece.moveto(positions[1], instant) + Globals.reset_halfmove() Utils.add_move(san_to_add) stop_input = false if set_input else stop_input + + +func load_fen(fen: String, visible: bool = true): + var data: Dictionary = Fen.parse(fen) + load_matrix(data.mat, visible) + Globals.turn = data.turn + Globals.fullmove = data.fullmove + Globals.halfmove = data.halfmove + + +func load_matrix(mat: Array, visible: bool = true): + if visible: + kill_pieces() + for x in range(8): + for y in range(8): + var ret = from_str_with_team(mat[y][x]) + if ret[0] != -1: + make_piece(Vector2(x, y), ret[0], ret[1], visible) + + +func from_str_with_team(string: String) -> Array: + var result = SanParser.from_str(string) + if result != -1: + return [result, true] + result = SanParser.from_str(string.to_upper()) + return [result, false] + + +func kill_pieces(): + for i in pieces.get_children(): + i.free() + kill_matrix() + + +func kill_matrix(): + matrix = [] + init_matrix() + + +func undo(): + Globals.turn = true + Globals.fullmove = 1 + Globals.halfmove = 0 + Globals.in_check = false + Globals.checking_piece = null + clear_fx() + play_pgn(Utils.pop_move(), true) diff --git a/FEN/Fen.gd b/FEN/Fen.gd new file mode 100644 index 0000000..da02550 --- /dev/null +++ b/FEN/Fen.gd @@ -0,0 +1,83 @@ +extends Node +class_name FEN + + +func parse(fen: String) -> Dictionary: + var reg = RegEx.new() + reg.compile( + "^(?<pieces>([pnbrqkPNBRQK1-8]{1,8}\\/?){8})\\s+(?<turn>b|w)\\s+(?<castling>-|K?Q?k?q)\\s+(?<enpassant>-|[a-h][3-6])\\s+(?<halfmove>\\d+)\\s+(?<fullmove>\\d+)\\s*$" + ) + var res = reg.search(fen) + if res: + var mat: Array = [] + var rows = res.strings[res.names.pieces].split("/") + for row in rows: + var append_row: Array = [] + for col in row: + if int(col) != 0: + for _i in range(int(col)): + append_row.append("") + else: + append_row.append(col) + mat.append(append_row) + var fenobj = { + "mat": mat, + "turn": res.strings[res.names.turn] == "w", + "castling": res.strings[res.names.castling], + "enpassant": res.strings[res.names.enpassant], + "halfmove": int(res.strings[res.names.halfmove]), + "fullmove": int(res.strings[res.names.fullmove]) + } + return fenobj + else: + Log.err("bad fen") + return {} + + +func get_fen() -> String: + var pieces := "" + for rank in range(8): + var empty := 0 + for file in range(8): + var spot: Piece = Globals.grid.matrix[rank][file] + if spot == null: + empty += 1 + if len(pieces) > 0 and str(empty - 1) == pieces[-1]: + pieces[-1] = str(empty) + else: + pieces += str(empty) + else: + pieces += ( + spot.shortname[0].to_upper() + if spot.white + else spot.shortname[0].to_lower() + ) + empty = 0 + if rank != 7: + pieces += "/" + # handle castling checks + var whitecastling := PoolStringArray(Globals.white_king.castleing(true)).join("") + var blackcastling := PoolStringArray(Globals.black_king.castleing(true)).join("") + var castlingrights := "" + if blackcastling and whitecastling: + castlingrights = whitecastling.to_upper() + blackcastling.to_lower() + else: + castlingrights = "-" + # castling rights are slightly janke + + var enpassants := "" + for pawn in Globals.pawns: + if pawn.just_double_stepped and pawn.just_set: + enpassants = Utils.to_algebraic(pawn.real_position + (Vector2.DOWN * pawn.whiteint)) + break + return ( + "%s %s %s %s %s %s" + % [ + pieces, + "w" if Globals.turn else "b", + castlingrights, + enpassants if enpassants else "-", + Globals.halfmove, + Globals.fullmove, + ] + ) # pos # turn # castling # enpassant # halfmove # fullmove @@ -33,14 +33,6 @@ func reset_vars() -> void: Utils.reset_vars() -func pack_vars() -> Dictionary: - return { - "fullmove": fullmove, - "halfmove": halfmove, - "turn": turn, - } - - func reset_halfmove() -> void: halfmove = 0 __nosethalfmove = true @@ -48,19 +40,17 @@ func reset_halfmove() -> void: func add_turn() -> void: Events.emit_signal("just_before_turn_over") - if !turn: + turn = not turn + if turn: # white just moved fullmove += 1 if __nosethalfmove: __nosethalfmove = false else: halfmove += 1 - turn = not turn Events.emit_signal("turn_over") -func get_turn(flip := false) -> String: - if flip: - return "black" if turn else "white" +func get_turn() -> String: return "white" if turn else "black" @@ -37,7 +37,6 @@ func parse(string) -> Dictionary: return {} else: break - var movetext := PoolStringArray() while !lines.empty(): var line = lines.pop_front().strip_edges() diff --git a/PGN/test_pgns.gd b/PGN/test_pgns.gd index 3144beb..2b727aa 100644 --- a/PGN/test_pgns.gd +++ b/PGN/test_pgns.gd @@ -15,7 +15,7 @@ func _load(i: int): var boar = load("res://Game.tscn").instance() get_tree().get_root().add_child(boar) boar = boar.get_board() - boar.play_pgn(pgns[i]) + boar.play_pgn(pgns[i], true) get_parent().hide() diff --git a/SanParse/Move.gd b/SanParse/Move.gd index afa07a9..f6e9ca1 100644 --- a/SanParse/Move.gd +++ b/SanParse/Move.gd @@ -21,7 +21,7 @@ static func castle_type(type: String) -> int: return MoveKind.CASTLETYPES.QUEEN_SIDE if type == "O-O-O" else MoveKind.CASTLETYPES.KING_SIDE -func set_check_type(type: String) -> void: +func set_check_type(type: String) -> Move: match type: "+": check_type = CHECKTYPES.CHECK @@ -29,6 +29,7 @@ func set_check_type(type: String) -> void: check_type = CHECKTYPES.CHECKMATE _: check_type = CHECKTYPES.NONE + return self func compile() -> String: # compiles the structure to a san @@ -37,11 +38,11 @@ func compile() -> String: # compiles the structure to a san MoveKind.CASTLE: res += move_kind.to_str() MoveKind.NORMAL: - res += Utils.to_str(piece) + res += to_str(piece) res += Utils.to_algebraic(move_kind.data[0]) res += "x" if is_capture else "" res += Utils.to_algebraic(move_kind.data[1]) - res += "=" + Utils.to_str(promotion) if promotion != -1 else "" + res += "=" + to_str(promotion) if promotion != -1 else "" match check_type: CHECKTYPES.CHECK: res += "+" @@ -50,6 +51,10 @@ func compile() -> String: # compiles the structure to a san return res.strip_edges() +static func to_str(type: int) -> String: + return " NBRQK"[type].strip_edges() # if its a pawn, return nothing + + ## tests # print(Utils.to_algebraic(make_long(SanParse.parse("e4").move_kind.data, false, SanParser.PAWN)[0]) == "e2") # print(Utils.to_algebraic(make_long(SanParse.parse("Nbc3").move_kind.data, false, SanParser.KNIGHT)[0]) == "b1") @@ -60,15 +65,15 @@ func compile() -> String: # compiles the structure to a san # print(Utils.to_algebraic(make_long(SanParse.parse("Nbxc3").move_kind.data, false, SanParser.KNIGHT)[0]) == "b1") # print(Utils.to_algebraic(make_long(SanParse.parse("N1xc3").move_kind.data, false, SanParser.KNIGHT)[0]) == "b1") ### fix short san -func make_long(): +func make_long() -> Move: if move_kind.type == MoveKind.CASTLE: - return + return self var newvecs: PoolVector2Array = [] var vectors = move_kind.data if Piece.is_on_board(vectors[0]): # [0] is the only one with -1(s) possible - return vectors + return self if is_capture: newvecs.append(long_helper(vectors[0], true, false, vectors[1])) @@ -77,10 +82,11 @@ func make_long(): if newvecs.empty(): Log.error("cruddlesticks") - return + return self newvecs.append(vectors[1]) move_kind.data = newvecs + return self func long_helper(vec: Vector2, attack: bool, move: bool, touch: Vector2): @@ -103,7 +109,11 @@ func long_helper(vec: Vector2, attack: bool, move: bool, touch: Vector2): func long_helper_helper(spot, touch, attack, move): - return Utils.spotispiece(piece, spot) and spot.white == Globals.turn and spot.can_touch(touch, attack, move) + return ( + Utils.spotispiece(piece, spot) + and spot.white == Globals.turn + and spot.can_touch(touch, attack, move) + ) class MoveKind: diff --git a/SanParse/SanParse.gd b/SanParse/SanParse.gd index a93b430..cd7d889 100644 --- a/SanParse/SanParse.gd +++ b/SanParse/SanParse.gd @@ -30,7 +30,7 @@ const UNKNOWN_POS = Vector2(-1, -1) enum { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING } -func from_str(string: String) -> int: +static func from_str(string: String) -> int: var find = " NBRQK".find(string) if find != -1: return find @@ -54,15 +54,13 @@ func regexmatch(san: String) -> Move: var re: RegExMatch = regexs.pawn_move.search(san) if re: var cap = re.strings - var mov = Move.new(PAWN, [UNKNOWN_POS, pos(cap[1], cap[2])]) - mov.set_check_type(cap[3]) + var mov = Move.new(PAWN, [UNKNOWN_POS, pos(cap[1], cap[2])]).set_check_type(cap[3]) return mov re = regexs.long_pawn_move.search(san) if re: var cap = re.strings - var mov = Move.new(PAWN, [pos(cap[1], cap[2]), pos(cap[3], cap[4])]) - mov.set_check_type(cap[5]) + var mov = Move.new(PAWN, [pos(cap[1], cap[2]), pos(cap[3], cap[4])]).set_check_type(cap[5]) return mov re = regexs.piece_movement.search(san) @@ -75,14 +73,18 @@ func regexmatch(san: String) -> Move: re = regexs.specific_row_piece_movement.search(san) if re: var cap = re.strings - var mov = Move.new(from_str(cap[1]), [Vector2(-1, Utils.row_pos(cap[2])), pos(cap[3], cap[4])]) + var mov = Move.new( + from_str(cap[1]), [Vector2(-1, Utils.row_pos(cap[2])), pos(cap[3], cap[4])] + ) mov.set_check_type(cap[5]) return mov re = regexs.specific_column_piece_movement.search(san) if re: var cap = re.strings - var mov = Move.new(from_str(cap[1]), [Vector2(Utils.col_pos(cap[2]), -1), pos(cap[3], cap[4])]) + var mov = Move.new( + from_str(cap[1]), [Vector2(Utils.col_pos(cap[2]), -1), pos(cap[3], cap[4])] + ) mov.set_check_type(cap[5]) return mov @@ -119,14 +121,18 @@ func regexmatch(san: String) -> Move: re = regexs.specific_column_piece_capture.search(san) if re: var cap = re.strings - var mov = Move.new(from_str(cap[1]), [Vector2(Utils.col_pos(cap[2]), -1), pos(cap[3], cap[4])], true) + var mov = Move.new( + from_str(cap[1]), [Vector2(Utils.col_pos(cap[2]), -1), pos(cap[3], cap[4])], true + ) mov.set_check_type(cap[5]) return mov re = regexs.specific_row_piece_capture.search(san) if re: var cap = re.strings - var mov = Move.new(from_str(cap[1]), [Vector2(-1, Utils.row_pos(cap[2])), pos(cap[3], cap[4])], true) + var mov = Move.new( + from_str(cap[1]), [Vector2(-1, Utils.row_pos(cap[2])), pos(cap[3], cap[4])], true + ) mov.set_check_type(cap[5]) return mov @@ -3,6 +3,7 @@ extends Node var internet := false signal newmove(move) signal newfen(fen) +signal pop_move(fen, was_num) var moves_list: PoolStringArray = [] var fen := "" @@ -13,56 +14,21 @@ func get_pgn(): func _on_turn_over() -> void: - fen = get_fen() + fen = Fen.get_fen() Log.info("fen: " + fen) emit_signal("newfen", fen) -func spotispiece(piece_type: int, spot: Piece) -> bool: - return SanParse.from_str(spot.shortname.to_upper()) == piece_type if spot else false +func pop_move() -> String: + emit_signal("pop_move") + moves_list.remove(moves_list.size() - 1) + var pgn = get_pgn() + moves_list.resize(0) + return pgn -func get_fen() -> String: - var pieces := "" - for rank in range(8): - var empty := 0 - for file in range(8): - var spot: Piece = Globals.grid.matrix[rank][file] - if spot == null: - empty += 1 - if len(pieces) > 0 and str(empty - 1) == pieces[-1]: - pieces[-1] = str(empty) - else: - pieces += str(empty) - else: - pieces += (spot.shortname[0].to_upper() if spot.white else spot.shortname[0].to_lower()) - empty = 0 - if rank != 7: - pieces += "/" - # handle castling checks - var whitecastling := PoolStringArray(Globals.white_king.castleing(true)).join("") - var blackcastling := PoolStringArray(Globals.black_king.castleing(true)).join("") - var castlingrights := "" - if blackcastling and whitecastling: - castlingrights = whitecastling.to_upper() + blackcastling.to_lower() - else: - castlingrights = "-" - - var enpassants := "" - for pawn in Globals.pawns: - if pawn.just_double_stepped and pawn.just_set: - enpassants += Utils.to_algebraic(pawn.real_position + (Vector2.DOWN * pawn.whiteint)) - return ( - "%s %s %s %s %s %s" - % [ - pieces, - "w" if Globals.turn else "b", - castlingrights, - enpassants if enpassants else "-", - Globals.halfmove, - Globals.fullmove, - ] - ) # pos # turn # castling # enpassant # halfmove # fullmove +func spotispiece(piece_type: int, spot: Piece) -> bool: + return SanParse.from_str(spot.shortname.to_upper()) == piece_type if spot else false static func str_bool(string: String) -> bool: @@ -94,7 +60,6 @@ func get_args() -> Dictionary: func _ready() -> void: - internet_available() Events.connect("turn_over", self, "_on_turn_over") if "help" in get_args(): print("usage: ./chess%s [debug | help]" % exec_ext()) @@ -103,6 +68,16 @@ func _ready() -> void: get_tree().quit() # dont wait Debug.monitor(self, "fen") Debug.monitor(self, "pgn", "get_pgn()") + var t = Timer.new() + add_child(t) + t.name = "t" + t.connect("timeout", self, "_on_timeout", [t]) + _on_timeout(t) + + +func _on_timeout(timer: Timer) -> void: + timer.start(600) # every 10m + request() # ping server so it doesnt go down static func exec_ext() -> String: @@ -146,14 +121,14 @@ static func get_node_name(node: Node) -> Array: return ["", ""] -func internet_available() -> bool: +func request() -> int: # returns err var http := HTTPRequest.new() add_child(http) - var httpurl := "https://1.1.1.1" - var returnable := http.request(httpurl) == OK - http.queue_free() - internet = returnable - return returnable + var httpurl := Network.url.replace("wss://", "http://") + var error := http.request(httpurl) + http.free() + internet = error == OK + return error func walk_dir(path := "res://assets/pieces", only_dir := true, exclude := []) -> PoolStringArray: # walk the directory, finding the asset packs @@ -182,7 +157,10 @@ func format_seconds(time: float, use_milliseconds: bool = false) -> String: func _notification(what: int) -> void: - if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST or what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: + if ( + what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST + or what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST + ): Log.debug("Bye!") @@ -205,9 +183,10 @@ static func from_algebraic(pos: String) -> Vector2: static func to_str(type: int) -> String: - return " NBRQK"[type].strip_edges() # if its a pawn, return nothing + return "PNBRQK"[type] +# cant wait for 4.0 dict.merge(dict) :C static func append_dict(dict: Dictionary, newdict: Dictionary) -> Dictionary: for key in newdict: dict[key] = newdict[key] diff --git a/assets/ui/draw.png b/assets/ui/draw.png index 5c1cae7..8e9cf86 100644 --- a/assets/ui/draw.png +++ b/assets/ui/draw.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:938a831facef14c00c2cfd69898cf62aef52c3f33a9e3353322a21d13682e388 -size 644 +oid sha256:1de02f935449aa8801afe6bd3595cc1e910af713869c3153b5eb20dfc2a686a1 +size 3806 diff --git a/assets/ui/flag.png.import b/assets/ui/flag.png.import index 4b333f1..c4498fe 100644 --- a/assets/ui/flag.png.import +++ b/assets/ui/flag.png.import @@ -20,7 +20,7 @@ compress/hdr_mode=0 compress/bptc_ldr=0 compress/normal_map=0 flags/repeat=0 -flags/filter=true +flags/filter=false flags/mipmaps=false flags/anisotropic=false flags/srgb=2 diff --git a/assets/ui/svg/draw.svg b/assets/ui/svg/draw.svg index 27c7f10..6f57116 100644 --- a/assets/ui/svg/draw.svg +++ b/assets/ui/svg/draw.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba8a0b64ab69629c16aa711fb491e18d6faa118c8a1aa9f7af3152d346198a2b -size 1519 +oid sha256:9bd1bcd279dfa4ae4e7e77ea1aef70b74b12fcbc4049032e6f0dccfea413193e +size 2699 diff --git a/assets/ui/svg/undo.svg b/assets/ui/svg/undo.svg new file mode 100644 index 0000000..1e9ba7f --- /dev/null +++ b/assets/ui/svg/undo.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6de601cb55b98d499b61c94c1b54274469ada71dc4500a6acc594d0519c5cb62 +size 1773 diff --git a/assets/ui/undo.png b/assets/ui/undo.png new file mode 100644 index 0000000..99cec56 --- /dev/null +++ b/assets/ui/undo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b21bf6fd833e8212031384a2c8df03eea5dc2a2244401e3e4f99878558644142 +size 3291 diff --git a/assets/ui/undo.png.import b/assets/ui/undo.png.import new file mode 100644 index 0000000..9c9d227 --- /dev/null +++ b/assets/ui/undo.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/undo.png-1a08869722195ead3fe48ffb6aa0ef0b.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/ui/undo.png" +dest_files=[ "res://.import/undo.png-1a08869722195ead3fe48ffb6aa0ef0b.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/export_presets.cfg b/export_presets.cfg index 5316d5b..e3fd4f7 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -7,7 +7,7 @@ custom_features="" export_filter="all_resources" include_filter="COPYING.md, LICENSE" exclude_filter="" -export_path="exports/chess.zip" +export_path="" script_export_mode=1 script_encryption_key="" @@ -40,7 +40,7 @@ custom_features="" export_filter="all_resources" include_filter="COPYING.md, LICENSE" exclude_filter="assets/pieces/alpha/*, assets/pieces/cburnett/*, assets/pieces/fresca/*, assets/pieces/governor/*, assets/pieces/horsey/*, assets/pieces/libra/*, assets/pieces/maestro/*, assets/pieces/pixel/*" -export_path="exports/chess.html" +export_path="" script_export_mode=1 script_encryption_key="" @@ -70,12 +70,12 @@ progressive_web_app/background_color=Color( 0, 0, 0, 1 ) name="Windows" platform="Windows Desktop" -runnable=false +runnable=true custom_features="" export_filter="all_resources" include_filter="COPYING.md, LICENSE" exclude_filter="" -export_path="exports/chess.exe" +export_path="" script_export_mode=1 script_encryption_key="" @@ -116,7 +116,7 @@ custom_features="" export_filter="all_resources" include_filter="COPYING.md, LICENSE" exclude_filter="" -export_path="exports/linux/chess.x86_64" +export_path="" script_export_mode=1 script_encryption_key="" diff --git a/networking/Network.gd b/networking/Network.gd index 7b19c8f..8496386 100644 --- a/networking/Network.gd +++ b/networking/Network.gd @@ -15,11 +15,11 @@ const HEADERS := { "relay": "R", # relay goes to both "signal": "S", # signal is one way "loadpgn": "L", # server telling me to load a pgn - "info": "I" + "info": "I", + "move": "M", + "undo": "<" } -const MOVEHEADERS := {move = "M", take = "K", castle = "C", passant = "P", promote = "Q"} # subheaders for HEADERS.move - const RELAYHEADERS := {chat = "C"} const SIGNALHEADERS := {takeback = "T", draw = "D", resign = "R", info = "I"} # subheaders for HEADERS.signal @@ -33,6 +33,7 @@ signal game_over(problem, isok) signal connection_established signal signal_recieved(what) signal chat(text) +signal undo(undo) signal info_recieved(info) ## for accounts(mostly) @@ -82,25 +83,29 @@ func _connection_error() -> void: func signal(body: Dictionary, header: String, _mainheader := HEADERS.signal) -> Dictionary: - var data: Dictionary = Utils.append_dict({"type": header, "gamecode": game_code}, body) - send_packet(data, _mainheader) + var data: Dictionary = Utils.append_dict({"type": header}, body) + send_gamecode_packet(data, _mainheader) return data func join_game(game: String) -> void: - send_packet(Utils.append_dict({"gamecode": game}, SaveLoad.get_public_info()), HEADERS.joinrequest) + send_gamecode_packet(SaveLoad.get_public_info(), HEADERS.joinrequest, game) func host_game(game: String) -> void: - send_packet(Utils.append_dict({"gamecode": game}, SaveLoad.get_public_info()), HEADERS.hostrequest) + send_gamecode_packet(SaveLoad.get_public_info(), HEADERS.hostrequest, game) + + +func send_gamecode_packet(data: Dictionary, header: String, gamecode: String = game_code): + send_packet(Utils.append_dict({"gamecode": gamecode}, data), header) -func relay_signal(body: Dictionary, header: String) -> Dictionary: # its really the same thing as signal() - return signal(body, header, HEADERS.relay) +func relay_signal(body: Dictionary, header: String, _mainheader := HEADERS.relay) -> Dictionary: # its really the same thing as signal() + return signal(body, header, _mainheader) func send_mov(mov: Move): - relay_signal({"move": mov.compile()}, MOVEHEADERS.move) + send_packet({move = mov.compile(), gamecode = game_code}, HEADERS.move) func stopgame(reason: String) -> void: @@ -108,12 +113,16 @@ func stopgame(reason: String) -> void: func _data_recieved() -> void: - if !OS.is_window_focused(): - OS.request_attention() var recieve: Dictionary = ws.get_peer(1).get_var() var header: String = recieve.header var text = recieve.data match header: + HEADERS.undo: + emit_signal("undo", text) + HEADERS.move: + if !OS.is_window_focused(): + OS.request_attention() + emit_signal("move_data", text.move) HEADERS.hostrequest: emit_signal("host_result", text) HEADERS.relay: @@ -121,8 +130,6 @@ func _data_recieved() -> void: match relay.type: RELAYHEADERS.chat: emit_signal("chat", relay) - MOVEHEADERS.move: - emit_signal("move_data", text.move) HEADERS.joinrequest: emit_signal("join_result", text) HEADERS.info: @@ -139,7 +146,9 @@ func _data_recieved() -> void: PacketHandler.leaving = false HEADERS.signal: var signal: Dictionary = text - emit_signal("signal_recieved", signal) + match signal.type: + _: + emit_signal("signal_recieved", signal) HEADERS.signup: emit_signal("signupresult", text) HEADERS.signin: diff --git a/pieces/Bishop.gd b/pieces/B.gd index 18029fb..18029fb 100644 --- a/pieces/Bishop.gd +++ b/pieces/B.gd diff --git a/pieces/King.gd b/pieces/K.gd index e0d56d7..ea253cc 100644 --- a/pieces/King.gd +++ b/pieces/K.gd @@ -4,8 +4,14 @@ class_name King, "res://assets/pieces/california/wK.png" var castle_check := true var can_castle := [] +enum { NONE, QUEEN_SIDE, KING_SIDE } # keep up to date with move.movekind.castletypes + func _ready() -> void: + if white: + Globals.white_king = self + else: + Globals.black_king = self Events.connect("just_before_turn_over", self, "just_before_over") @@ -26,18 +32,6 @@ func get_moves(no_enemys := false, check_spots_check := true) -> PoolVector2Arra func just_before_over() -> void: # assign metadata for threefold repetition draw check castleing() - if can_castle.size() > 0: - for i in can_castle: - if i[3] == "O-O-O": - if white: - Globals.grid.matrix[8].wccl = true - else: - Globals.grid.matrix[8].bccl = true - else: - if white: - Globals.grid.matrix[8].wccr = true - else: - Globals.grid.matrix[8].bccr = true func castleing(justcheckrooks := false) -> Array: @@ -68,25 +62,17 @@ func castleing(justcheckrooks := false) -> Array: var posx3 := pos_around(direction * 3) if at_pos(posx3) or checkcheck(posx3): continue - can_castle.append([posx2, rook, rook_motion[i], "O-O-O" if i == 1 else "O-O"]) + can_castle.append([posx2, rook, rook_motion[i], QUEEN_SIDE if i == 1 else KING_SIDE]) moves.append(posx2) if justcheckrooks: moves.sort() return moves -func castle(position: Vector2, instant := false) -> String: - var return_string := "" - if can_castle.size() == 1: - return_string = can_castle[0][3] - else: - for i in can_castle: - if i[0] == position: - return_string = i[3] - break +# basically a wrapper for move to +func castle(position: Vector2, instant := false) -> void: can_castle.clear() moveto(position, instant) - return return_string func can_move() -> bool: # checks if you can legally move diff --git a/pieces/Knight.gd b/pieces/N.gd index dfb05c8..dfb05c8 100644 --- a/pieces/Knight.gd +++ b/pieces/N.gd diff --git a/pieces/Pawn.gd b/pieces/P.gd index bc94c1c..c130948 100644 --- a/pieces/Pawn.gd +++ b/pieces/P.gd @@ -20,10 +20,11 @@ onready var popup: Popup = $Popup func _ready() -> void: Globals.pawns.append(self) Events.connect("turn_over", self, "_on_turn_over") - Events.connect("just_before_turn_over", self, "_just_before_turn_over") for i in range(4): # add 4 sprites var newsprite: TextureButton = load("res://ui/PromotionPreview.tscn").instance() - newsprite.texture_normal = load("%s%s%s.png" % [Globals.grid.ASSETS_PATH, team.to_lower(), promotables[i]]) + newsprite.texture_normal = load( + "%s%s%s.png" % [Globals.grid.ASSETS_PATH, team.to_lower(), promotables[i]] + ) newsprite.name = promotables[i] newsprite.connect("pressed", self, "_pressed", [newsprite.name]) previews.add_child(newsprite) @@ -34,7 +35,6 @@ func open_previews() -> void: var rect := popup.get_global_rect() rect.position = rect_global_position popup.popup(rect) - # popup.visible = true func _exit_tree() -> void: @@ -44,14 +44,9 @@ func _exit_tree() -> void: func moveto(position: Vector2, instant := false) -> void: # check if 2 step if !just_double_stepped and !has_moved: - if white and real_position.y - position.y == 2: - just_double_stepped = true - just_set = true - if !white and position.y - real_position.y == 2: - just_double_stepped = true - just_set = true + just_double_stepped = true + just_set = true .moveto(position, instant) - Globals.reset_halfmove() func get_moves(_var := false, check_spots_check := true) -> PoolVector2Array: @@ -61,9 +56,10 @@ func get_moves(_var := false, check_spots_check := true) -> PoolVector2Array: var point: Vector2 = points[i] point *= whiteint point = pos_around(point) - if is_on_board(point) and at_pos(point) == null: - if i == 1 and has_moved or at_pos(pos_around(points[0] * whiteint)) != null: - continue + if at_pos(point) == null: + if i == 1: + if has_moved or at_pos(pos_around(points[0] * whiteint)) != null: + continue if check_spots_check and checkcheck(point): continue if is_on_board(point): @@ -108,9 +104,9 @@ func en_passant(turncheck := true, check_spots_check := true) -> Array: # in pa var passants := [pos_around(Vector2.LEFT), pos_around(Vector2.RIGHT)] var moves := [] for i in passants: - if !is_on_board(i) or !at_pos(i): - continue var spot := at_pos(i) + if !spot: + continue if spot.white == white or !Utils.is_pawn(spot): continue if turncheck and white != Globals.turn: @@ -136,11 +132,11 @@ func promote(position: Vector2, type: String) -> void: open_previews() -func promote_to(promote_to: String, is_capture: bool, position: Vector2, instant := false): +func promote_to(promote_to: int, is_capture: bool, position: Vector2, instant := false): if is_capture and at_pos(position): at_pos(position).took(instant) clear_clicked() - Globals.grid.make_piece(position, piece(promote_to), white) + Globals.grid.make_piece(position, promote_to, white) took() @@ -153,39 +149,9 @@ func _pressed(promote_to: String) -> void: Globals.network.send_mov(mov) -static func piece(string: String) -> String: - match string: - "Q": - return "Queen" - "N": - return "Knight" - "R": - return "Rook" - "B": - return "Bishop" - _: - return "Piece" - - func _on_turn_over() -> void: if just_set: just_set = false return if just_double_stepped: just_double_stepped = false - - -func _just_before_turn_over() -> void: - var had_a_enpassant := len(enpassant) > 0 - enpassant.clear() - if !had_a_enpassant: # scuffed method to check if enpassant is possible - en_passant(false) - var temporary := [] - for i in enpassant: - temporary.append(i[0]) - if !temporary: - return - if white: - Globals.grid.matrix[8].wcep.append_array(temporary) - else: - Globals.grid.matrix[8].bcep.append_array(temporary) diff --git a/pieces/Piece.gd b/pieces/Piece.gd index 6764e62..da61d93 100644 --- a/pieces/Piece.gd +++ b/pieces/Piece.gd @@ -27,6 +27,8 @@ func _ready() -> void: rect_pivot_offset = Globals.grid.piece_size / 2 frame.modulate = Globals.grid.overlay_color colorrect.color = Globals.grid.overlay_color + sprite.flip_v = Globals.grid.flipped + sprite.flip_h = Globals.grid.flipped load_texture() @@ -34,7 +36,9 @@ func set_zindex(zindex: int, obj: CanvasItem = self): VisualServer.canvas_item_set_z_index(obj.get_canvas_item(), zindex) -func load_texture(path := "%s%s%s.png" % [Globals.grid.ASSETS_PATH, team, shortname.to_upper()]) -> void: +func load_texture( + path := "%s%s%s.png" % [Globals.grid.ASSETS_PATH, team, shortname.to_upper()] +) -> void: sprite.texture = load(path) @@ -51,7 +55,12 @@ func clear_clicked() -> void: func move(newpos: Vector2) -> void: # dont use directly; use moveto tween.interpolate_property( - self, "rect_position", rect_position, newpos * Globals.grid.piece_size, 0.3, Tween.TRANS_BACK + self, + "rect_position", + rect_position, + newpos * Globals.grid.piece_size, + 0.3, + Tween.TRANS_BACK ) var signresult := int(sign(real_position.x - newpos.x)) if signresult == 1: @@ -69,7 +78,13 @@ func moveto(pos: Vector2, instant := false) -> void: move(pos) real_position = pos SoundFx.play("Move") + else: has_moved = true + real_position = pos + + +func update_visual_position(): + rect_position = real_position * Globals.grid.piece_size func pos_around(around_vector: Vector2) -> Vector2: @@ -91,7 +106,9 @@ static func all_dirs() -> PoolVector2Array: ) -func traverse(arr: PoolVector2Array = [], no_enemys := false, check_spots_check := true) -> PoolVector2Array: +func traverse( + arr: PoolVector2Array = [], no_enemys := false, check_spots_check := true +) -> PoolVector2Array: var circle_array: PoolVector2Array = [] for i in arr: var pos := real_position @@ -198,7 +215,6 @@ func take(piece: Piece, instant := false) -> void: clear_clicked() piece.took(instant) moveto(piece.real_position, instant) - Globals.reset_halfmove() func took(instant := false) -> void: # called when piece is taken diff --git a/pieces/Queen.gd b/pieces/Q.gd index 6143b16..6143b16 100644 --- a/pieces/Queen.gd +++ b/pieces/Q.gd diff --git a/pieces/Rook.gd b/pieces/R.gd index 40150d8..ed81f62 100644 --- a/pieces/Rook.gd +++ b/pieces/R.gd @@ -3,4 +3,6 @@ class_name Rook, "res://assets/pieces/california/wR.png" func get_moves(no_enemys := false, check_spots_check := true) -> PoolVector2Array: - return traverse([Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT], no_enemys, check_spots_check) + return traverse( + [Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT], no_enemys, check_spots_check + ) diff --git a/project.godot b/project.godot index fa975ae..bbd5ca7 100644 --- a/project.godot +++ b/project.godot @@ -17,7 +17,7 @@ _global_script_classes=[ { "base": "Piece", "class": "Bishop", "language": "GDScript", -"path": "res://pieces/Bishop.gd" +"path": "res://pieces/B.gd" }, { "base": "Control", "class": "ColorPickerBetter", @@ -40,10 +40,20 @@ _global_script_classes=[ { "path": "res://ui/confirm/confirm.gd" }, { "base": "BarTextureButton", +"class": "ConfirmButton", +"language": "GDScript", +"path": "res://ui/barbutton/confirmbutton.gd" +}, { +"base": "ConfirmButton", "class": "DrawButton", "language": "GDScript", "path": "res://ui/barbutton/drawbutton.gd" }, { +"base": "Node", +"class": "FEN", +"language": "GDScript", +"path": "res://FEN/Fen.gd" +}, { "base": "LineEdit", "class": "FENLabel", "language": "GDScript", @@ -77,12 +87,12 @@ _global_script_classes=[ { "base": "Piece", "class": "King", "language": "GDScript", -"path": "res://pieces/King.gd" +"path": "res://pieces/K.gd" }, { "base": "Piece", "class": "Knight", "language": "GDScript", -"path": "res://pieces/Knight.gd" +"path": "res://pieces/N.gd" }, { "base": "Control", "class": "Lobby", @@ -117,7 +127,7 @@ _global_script_classes=[ { "base": "Piece", "class": "Pawn", "language": "GDScript", -"path": "res://pieces/Pawn.gd" +"path": "res://pieces/P.gd" }, { "base": "Control", "class": "Piece", @@ -132,9 +142,9 @@ _global_script_classes=[ { "base": "Piece", "class": "Queen", "language": "GDScript", -"path": "res://pieces/Queen.gd" +"path": "res://pieces/Q.gd" }, { -"base": "BarTextureButton", +"base": "ConfirmButton", "class": "ResignButton", "language": "GDScript", "path": "res://ui/barbutton/resignbutton.gd" @@ -147,7 +157,7 @@ _global_script_classes=[ { "base": "Piece", "class": "Rook", "language": "GDScript", -"path": "res://pieces/Rook.gd" +"path": "res://pieces/R.gd" }, { "base": "Node", "class": "SanParser", @@ -164,6 +174,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://ui/Status.gd" }, { +"base": "ConfirmButton", +"class": "UndoButton", +"language": "GDScript", +"path": "res://ui/barbutton/undobutton.gd" +}, { "base": "Control", "class": "UsernamePass", "language": "GDScript", @@ -176,7 +191,9 @@ _global_script_class_icons={ "ColorPickerButtonBetter": "", "ColorSelect": "", "Confirm": "", +"ConfirmButton": "", "DrawButton": "res://assets/ui/draw.png", +"FEN": "", "FENLabel": "", "FlipButton": "res://assets/ui/flip_board.png", "Grid": "", @@ -201,6 +218,7 @@ _global_script_class_icons={ "SanParser": "", "SaveLoader": "", "StatusLabel": "", +"UndoButton": "res://assets/ui/undo.png", "UsernamePass": "" } @@ -229,6 +247,7 @@ Debug="*res://Debug.gd" SanParse="*res://SanParse/SanParse.gd" Pgn="*res://PGN/PGN.gd" Log="*res://Log.gd" +Fen="*res://FEN/Fen.gd" [debug] @@ -251,6 +270,10 @@ main_run_args="debug" enabled=PoolStringArray( ) +[gui] + +theme/custom="res://ui/theme/main.tres" + [importer_defaults] texture={ @@ -19,7 +19,9 @@ class TestSan: func assert_capture(mv: String, start: Vector2, dest: Vector2, piece: int) -> void: assert_all(parse(mv), PoolVector2Array([start, dest]), piece, true) - func assert_all(mv: Move, vectors: PoolVector2Array, piece: int, capture: bool, promote = -1) -> void: + func assert_all( + mv: Move, vectors: PoolVector2Array, piece: int, capture: bool, promote = -1 + ) -> void: assert(mv.move_kind.type == Move.MoveKind.NORMAL) assert([mv.move_kind.data == vectors, mv.piece == piece, mv.is_capture == capture].min()) if promote != -1: @@ -60,7 +62,9 @@ class TestSan: assert_capture("exd4", Vector2(4, -1), Vector2(3, 4), PAWN) func test_pawn_capture_promotion(): - assert_all(parse("exd8=Q"), PoolVector2Array([Vector2(4, -1), Vector2(3, 0)]), PAWN, true, QUEEN) + assert_all( + parse("exd8=Q"), PoolVector2Array([Vector2(4, -1), Vector2(3, 0)]), PAWN, true, QUEEN + ) func test_pawn_capture_long(): assert_capture("e3xd4", Vector2(4, 5), Vector2(3, 4), PAWN) diff --git a/ui/barbutton/confirmbutton.gd b/ui/barbutton/confirmbutton.gd new file mode 100644 index 0000000..d04e2ce --- /dev/null +++ b/ui/barbutton/confirmbutton.gd @@ -0,0 +1,38 @@ +extends BarTextureButton +class_name ConfirmButton + +const Confirm = preload("res://ui/confirm/Confirm.tscn") +var waiting_on_answer: Confirm = null +export(String) var confirm_text = "" + +export(NodePath) onready var status = get_node(status) as StatusLabel + + +func _ready() -> void: + PacketHandler.connect("game_over", self, "set_disabled", [true]) + if Globals.network: + Globals.network.connect("signal_recieved", self, "_signal_recieved") + + +func confirm() -> void: + var confirm = Confirm.instance() + add_child(confirm) + confirm.confirm(self, confirm_text, 20) + waiting_on_answer = confirm + + +func _signal_recieved(_signal: Dictionary) -> void: + pass + + +func _confirmed(what: bool) -> void: + if waiting_on_answer: + if !waiting_on_answer.is_queued_for_deletion(): + waiting_on_answer.queue_free() + waiting_on_answer = null + if what: + after_confirmed() + + +func after_confirmed() -> void: + pass diff --git a/ui/barbutton/drawbutton.gd b/ui/barbutton/drawbutton.gd index 2003794..41d3c38 100644 --- a/ui/barbutton/drawbutton.gd +++ b/ui/barbutton/drawbutton.gd @@ -1,31 +1,16 @@ -extends BarTextureButton +extends ConfirmButton class_name DrawButton, "res://assets/ui/draw.png" -const Confirm = preload("res://ui/confirm/Confirm.tscn") -var waiting_on_answer: Confirm = null -export(NodePath) onready var status = get_node(status) as StatusLabel - - -func _ready() -> void: - PacketHandler.connect("game_over", self, "set_disabled", [true]) - if Globals.network: - Globals.network.connect("signal_recieved", self, "_on_signal") - - -func _on_signal(what: Dictionary) -> void: +func _signal_recieved(what: Dictionary) -> void: if what.type == Network.SIGNALHEADERS.draw: if "question" in what: - var confirm = Confirm.instance() - add_child(confirm) - confirm.confirm(self, "Your opponent wants to draw", 20) - waiting_on_answer = confirm + confirm() else: - disabled = false if what.accepted: drawed() else: - status.set_text("Your opponent rejected the draw") + status.set_text("Draw request rejected") func drawed() -> GDScriptFunctionState: @@ -36,17 +21,12 @@ func _pressed() -> void: if waiting_on_answer: _confirmed(true) else: - disabled = true Globals.network.signal({"question": ""}, Network.SIGNALHEADERS.draw) status.set_text("Draw request sent") -func _confirmed(yes: bool) -> void: # called from confirmbar.confirmed - if waiting_on_answer: - if !waiting_on_answer.is_queued_for_deletion(): - waiting_on_answer.queue_free() - disabled = false - waiting_on_answer = null - Globals.network.signal({"accepted": yes}, Network.SIGNALHEADERS.draw) - if yes: - drawed() +func _confirmed(what: bool) -> void: # called from confirmbar.confirmed + ._confirmed(what) + Globals.network.signal({"accepted": what}, Network.SIGNALHEADERS.draw) + if what: + drawed() diff --git a/ui/barbutton/resignbutton.gd b/ui/barbutton/resignbutton.gd index 6fdfee4..a9baed5 100644 --- a/ui/barbutton/resignbutton.gd +++ b/ui/barbutton/resignbutton.gd @@ -1,19 +1,8 @@ -extends BarTextureButton +extends ConfirmButton class_name ResignButton, "res://assets/ui/flag.png" -const Confirm = preload("res://ui/confirm/Confirm.tscn") -var waiting_on_answer: Confirm = null -export(NodePath) onready var status = get_node(status) as StatusLabel - - -func _ready() -> void: - PacketHandler.connect("game_over", self, "set_disabled", [true]) - if Globals.network: - Globals.network.connect("signal_recieved", self, "resigned") - - -func resigned(what: Dictionary) -> void: +func _signal_recieved(what: Dictionary) -> void: if what.type == Network.SIGNALHEADERS.resign: Globals.grid.win(Globals.team, "resignation") @@ -22,18 +11,10 @@ func _pressed() -> void: if waiting_on_answer: _confirmed(true) else: - var confirm = Confirm.instance() - add_child(confirm) - confirm.confirm(self, "Resign?", 20) - waiting_on_answer = confirm + confirm() -func _confirmed(what: bool) -> void: - if waiting_on_answer: - if !waiting_on_answer.is_queued_for_deletion(): - waiting_on_answer.queue_free() - waiting_on_answer = null - if what: - Globals.network.signal({}, Network.SIGNALHEADERS.resign) - Globals.grid.win(!Globals.team, "resignation") - disabled = true +func after_confirmed(): + Globals.network.signal({}, Network.SIGNALHEADERS.resign) + Globals.grid.win(!Globals.team, "resignation") + disabled = true diff --git a/ui/barbutton/undobutton.gd b/ui/barbutton/undobutton.gd new file mode 100644 index 0000000..72c2190 --- /dev/null +++ b/ui/barbutton/undobutton.gd @@ -0,0 +1,46 @@ +extends ConfirmButton +class_name UndoButton, "res://assets/ui/undo.png" + + +func _ready(): + Globals.network.connect("undo", self, "undo_recieved") + + +func _pressed(): + if waiting_on_answer: + _confirmed(true) + else: + if Utils.moves_list.size() == 0: + status.set_text("No moves to undo!") + return + elif Globals.turn == Globals.team: + status.set_text("It is your turn!") + return + Globals.network.send_packet( + {gamecode = Globals.network.game_code, question = ""}, Network.HEADERS.undo + ) + status.set_text("Undo request sent") + + +func undo_recieved(sig: Dictionary) -> void: + if "question" in sig: + confirm() + else: + if sig.accepted: + status.set_text("Undo request accepted") + undo() + else: + status.set_text("Undo request rejected") + + +func _confirmed(what: bool) -> void: + ._confirmed(what) + Globals.network.send_packet( + {gamecode = Globals.network.game_code, accepted = what}, Network.HEADERS.undo + ) + if what: + undo() + + +func undo(): + Globals.grid.undo() diff --git a/ui/chat/Chat.gd b/ui/chat/Chat.gd index 6b2a46a..e529720 100644 --- a/ui/chat/Chat.gd +++ b/ui/chat/Chat.gd @@ -40,7 +40,9 @@ func add_label_with(data): add_label(string) -func add_label(bbcode: String, name = "richtextlabel", size = Vector2(rect_size.x, 35)) -> RichTextLabel: +func add_label( + bbcode: String, name = "richtextlabel", size = Vector2(rect_size.x, 35) +) -> RichTextLabel: var l = RichTextLabel.new() l.name = name l.rect_min_size = size @@ -50,7 +52,9 @@ func add_label(bbcode: String, name = "richtextlabel", size = Vector2(rect_size. l.connect("meta_clicked", self, "open_url") l.bbcode_text = bbcode # l.fit_content_height = true - tween.interpolate_property(scrollbar, "value", scrollbar.value, scrollbar.max_value, .5, Tween.TRANS_BOUNCE) + tween.interpolate_property( + scrollbar, "value", scrollbar.value, scrollbar.max_value, .5, Tween.TRANS_BOUNCE + ) tween.start() return l @@ -67,7 +71,9 @@ func send(_arg = 0): text.text = "" var name_data = SaveLoad.get_data("id").name var name = name_data if name_data else "Anonymous" - Globals.network.relay_signal({"text": t, "who": name if name else "Anonymous"}, Network.RELAYHEADERS.chat) + Globals.network.relay_signal( + {"text": t, "who": name if name else "Anonymous"}, Network.RELAYHEADERS.chat + ) # markdown to bbcode diff --git a/ui/confirm/Confirm.tscn b/ui/confirm/Confirm.tscn index 6226aa9..b15ba1b 100644 --- a/ui/confirm/Confirm.tscn +++ b/ui/confirm/Confirm.tscn @@ -1,16 +1,18 @@ -[gd_scene load_steps=6 format=2] +[gd_scene load_steps=7 format=2] [ext_resource path="res://ui/theme/main.tres" type="Theme" id=1] [ext_resource path="res://ui/barbutton/BarTextureButton.tscn" type="PackedScene" id=2] [ext_resource path="res://assets/ui/close.png" type="Texture" id=3] [ext_resource path="res://ui/confirm/confirm.gd" type="Script" id=4] [ext_resource path="res://assets/ui/check.png" type="Texture" id=5] +[ext_resource path="res://ui/verdana-bold-small.tres" type="DynamicFont" id=6] [node name="Confirm" type="WindowDialog"] margin_right = 576.0 margin_bottom = 168.0 rect_min_size = Vector2( 700, 0 ) theme = ExtResource( 1 ) +custom_fonts/title_font = ExtResource( 6 ) popup_exclusive = true script = ExtResource( 4 ) diff --git a/ui/menus/StartMenu.tscn b/ui/menus/StartMenu.tscn index 99e0533..e282f34 100644 --- a/ui/menus/StartMenu.tscn +++ b/ui/menus/StartMenu.tscn @@ -24,9 +24,9 @@ script = ExtResource( 7 ) [node name="tabs" type="TabContainer" parent="."] margin_left = 89.0 -margin_top = 47.0 +margin_top = 23.0 margin_right = 1333.0 -margin_bottom = 753.0 +margin_bottom = 777.0 rect_min_size = Vector2( 2, 2 ) mouse_filter = 1 size_flags_horizontal = 0 diff --git a/ui/menus/sidebarright/SidebarRight.tscn b/ui/menus/sidebarright/SidebarRight.tscn index 78b3046..7393a0a 100644 --- a/ui/menus/sidebarright/SidebarRight.tscn +++ b/ui/menus/sidebarright/SidebarRight.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=29 format=2] +[gd_scene load_steps=31 format=2] [ext_resource path="res://ui/barbutton/drawbutton.gd" type="Script" id=1] [ext_resource path="res://ui/barbutton/resignbutton.gd" type="Script" id=2] @@ -14,8 +14,10 @@ [ext_resource path="res://assets/ui/Roboto-Medium.ttf" type="DynamicFontData" id=12] [ext_resource path="res://ui/menus/sidebarright/SidebarRight.gd" type="Script" id=13] [ext_resource path="res://ui/theme/main.tres" type="Theme" id=14] +[ext_resource path="res://assets/ui/undo.png" type="Texture" id=15] [ext_resource path="res://assets/ui/draw.png" type="Texture" id=16] [ext_resource path="res://assets/ui/flip_board.png" type="Texture" id=17] +[ext_resource path="res://ui/barbutton/undobutton.gd" type="Script" id=18] [ext_resource path="res://ui/barbutton/flipbutton.gd" type="Script" id=19] [ext_resource path="res://assets/ui/flag.png" type="Texture" id=20] [ext_resource path="res://ui/menus/sidebarright/sandisplay/SanDisplay.tscn" type="PackedScene" id=21] @@ -144,23 +146,36 @@ custom_constants/separation = 0 alignment = 1 [node name="FlipBoard" parent="V/buttonbarholder/Panel/buttonbar" instance=ExtResource( 6 )] -margin_left = 166.0 -margin_right = 216.0 +margin_left = 141.0 +margin_right = 191.0 +hint_tooltip = "rotate the board" texture_normal = ExtResource( 17 ) script = ExtResource( 19 ) [node name="DrawButton" parent="V/buttonbarholder/Panel/buttonbar" instance=ExtResource( 6 )] -margin_left = 216.0 -margin_right = 266.0 +margin_left = 191.0 +margin_right = 241.0 +hint_tooltip = "request a draw" texture_normal = ExtResource( 16 ) script = ExtResource( 1 ) +confirm_text = "Your opponent requests a draw" status = NodePath("../../../../Status") [node name="ResignButton" parent="V/buttonbarholder/Panel/buttonbar" instance=ExtResource( 6 )] -margin_left = 266.0 -margin_right = 316.0 +margin_left = 241.0 +margin_right = 291.0 +hint_tooltip = "resign" texture_normal = ExtResource( 20 ) script = ExtResource( 2 ) +confirm_text = "Resign?" +status = NodePath("../../../../Status") + +[node name="UndoButton" parent="V/buttonbarholder/Panel/buttonbar" instance=ExtResource( 6 )] +margin_left = 291.0 +margin_right = 341.0 +texture_normal = ExtResource( 15 ) +script = ExtResource( 18 ) +confirm_text = "Your opponent requests a takeback" status = NodePath("../../../../Status") [node name="SanDisplay" type="PanelContainer" parent="V"] diff --git a/ui/menus/sidebarright/Timer.gd b/ui/menus/sidebarright/Timer.gd index 0f128d3..e97d8d7 100644 --- a/ui/menus/sidebarright/Timer.gd +++ b/ui/menus/sidebarright/Timer.gd @@ -1,32 +1,22 @@ extends Node -var enabled := false +export(NodePath) var blacklabel +export(NodePath) var whitelabel +onready var labels = [get_node(blacklabel), get_node(whitelabel)] -var count := 0 +var turn_time := 0.0 -export(NodePath) onready var whitelabel = get_node(whitelabel) as Label -export(NodePath) onready var blacklabel = get_node(blacklabel) as Label +func _process(delta): + # int of false is 0 and true is 1 + turn_time += delta + labels[int(Globals.turn)].tick() -func _ready() -> void: - Events.connect("turn_over", self, "turn_over") - # disable, because they work wierdly with laggy and stuff - whitelabel.hide() # disable - blacklabel.hide() # disable - set_process(false) # disable +func _move_decided(): + prints("turn took", turn_time) + turn_time = 0.0 -func _process(delta: float) -> void: - if !enabled: - return - if Globals.turn: - if !whitelabel.set_time(whitelabel.time - delta): - enabled = false - else: - if !blacklabel.set_time(blacklabel.time - delta): - enabled = false - -func turn_over() -> void: - count += 1 - enabled = count >= 2 +func _ready(): + Globals.grid.connect("move_decided", self, "_move_decided") diff --git a/ui/menus/sidebarright/TimerLabels.gd b/ui/menus/sidebarright/TimerLabels.gd index 59e3374..81317a7 100644 --- a/ui/menus/sidebarright/TimerLabels.gd +++ b/ui/menus/sidebarright/TimerLabels.gd @@ -24,6 +24,10 @@ func set_time(newtime: float) -> bool: return true +func tick(delta: float): + time -= delta + + func _ready() -> void: set_time(STARTTIME) set_color() @@ -38,6 +42,14 @@ func _on_game_over() -> void: func set_color() -> void: if time > 10: - colorrect.color = (Globals.grid.clockrunning_color if Globals.turn == white else Color.transparent) + colorrect.color = ( + Globals.grid.clockrunning_color + if Globals.turn == white + else Color.transparent + ) else: - colorrect.color = (Globals.grid.clockrunninglow if Globals.turn == white else Globals.grid.clocklow) + colorrect.color = ( + Globals.grid.clockrunninglow + if Globals.turn == white + else Globals.grid.clocklow + ) diff --git a/ui/menus/sidebarright/sandisplay/SanDisplay.gd b/ui/menus/sidebarright/sandisplay/SanDisplay.gd index 58ddbad..dc8410f 100644 --- a/ui/menus/sidebarright/sandisplay/SanDisplay.gd +++ b/ui/menus/sidebarright/sandisplay/SanDisplay.gd @@ -13,24 +13,29 @@ func _ready() -> void: scroll_bar.step = .15 add_child(tween) Utils.connect("newmove", self, "on_new_move") + Utils.connect("pop_move", self, "reset_moves") func create_number_label(num: int) -> void: var base = Base.instance() sans.add_child(base) yield(get_tree(), "idle_frame") - base.number.text = "%s." % str(num) + base.number.text = "%s." % num + base.name = base.number.text func add_move_to_label(move: String) -> void: + if !Globals.turn: + create_number_label(Globals.fullmove) sans.get_children()[-1].add_move(move) func on_new_move(move: String) -> void: - if !Globals.turn: # black just moved - yield(create_number_label(Globals.fullmove), "completed") add_move_to_label(move) - tween.interpolate_property( # scrolldown - scroll_bar, "value", scroll_bar.value, scroll_bar.max_value, 0.5, Tween.TRANS_BOUNCE - ) + tween.interpolate_property(scroll_bar, "value", scroll_bar.value, scroll_bar.max_value, 0.5, 9) # bouncy tween.start() + + +func reset_moves(): + for i in sans.get_children(): + i.queue_free() |