online multiplayer chess game (note server currently down)
| -rw-r--r-- | Debug.gd | 2 | ||||
| -rw-r--r-- | Globals.gd | 1 | ||||
| -rw-r--r-- | Grid.gd | 73 | ||||
| -rw-r--r-- | PGN/PGN.gd | 38 | ||||
| -rw-r--r-- | PGN/test_pgns.gd | 26 | ||||
| -rw-r--r-- | PGN/test_pgns.tscn | 20 | ||||
| -rw-r--r-- | README.md | 7 | ||||
| -rw-r--r-- | SanParse/Move.gd | 12 | ||||
| -rw-r--r-- | SanParse/SanParse.gd | 4 | ||||
| -rw-r--r-- | Utils.gd | 13 | ||||
| -rw-r--r-- | assets/ui/circle.png | 3 | ||||
| -rw-r--r-- | assets/ui/circle.png.import | 35 | ||||
| -rw-r--r-- | assets/ui/svg/circle.svg | 3 | ||||
| -rw-r--r-- | assets/ui/svg/circle_part.svg | 3 | ||||
| -rw-r--r-- | export_presets.cfg | 2 | ||||
| -rw-r--r-- | html/custom.html | 280 | ||||
| -rw-r--r-- | networking/Network.gd | 36 | ||||
| -rw-r--r-- | networking/PacketHandler.gd | 60 | ||||
| -rw-r--r-- | pieces/King.gd | 4 | ||||
| -rw-r--r-- | pieces/Pawn.gd | 32 | ||||
| -rw-r--r-- | pieces/Piece.gd | 23 | ||||
| -rw-r--r-- | project.godot | 7 | ||||
| -rw-r--r-- | saveload.gd | 4 | ||||
| -rw-r--r-- | test.gd | 8 | ||||
| -rw-r--r-- | ui/Log.gd | 31 | ||||
| -rw-r--r-- | ui/Settings.gd | 12 | ||||
| -rw-r--r-- | ui/StartMenu.tscn | 6 | ||||
| -rw-r--r-- | ui/account/Account.gd | 64 | ||||
| -rw-r--r-- | ui/account/Account.tscn | 2 | ||||
| -rw-r--r-- | ui/account/usernamepass.gd | 21 | ||||
| -rw-r--r-- | ui/animations/LoadingAnimation.tscn | 9 |
31 files changed, 616 insertions, 225 deletions
@@ -14,7 +14,7 @@ const vertical := 15 static func is_debug() -> bool: var args := Utils.get_args() if "debug" in args: - return bool(args.debug) + return Utils.str_bool(args.debug) return OS.is_debug_build() @@ -55,7 +55,6 @@ func add_turn() -> void: else: halfmove += 1 turn = not turn - Log.debug("Turn over signal emmited") Events.emit_signal("turn_over") @@ -1,7 +1,7 @@ extends Node2D class_name Grid -const Piece := preload("res://Piece.tscn") +const PieceScene := preload("res://Piece.tscn") const Square := preload("res://Square.tscn") const BottomLeftLabel := preload("res://ui/boardlabels/BottomLeftLabel.tscn") const TopRightLabel := preload("res://ui/boardlabels/TopRightLabel.tscn") @@ -23,7 +23,7 @@ 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 stop_input := true var background_matrix := [] var history_matrixes := {} var last_clicked: Piece = null @@ -43,6 +43,8 @@ func _init() -> void: 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 @@ -52,6 +54,7 @@ func _ready() -> void: Debug.monitor(self, "last_clicked") Debug.monitor(self, "matrix", "matrix[8]") Debug.monitor(self, "highest value in 3fold", "threefoldrepetition()") + stop_input = false func _exit_tree() -> void: @@ -208,7 +211,7 @@ func init_matrix() -> void: # create the matrix func make_piece(position: Vector2, script: String, white: bool = true) -> void: # make peace - var piece := Piece.instance() # create a piece + 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.global_position = position * piece_size # set the global position @@ -369,15 +372,13 @@ func _on_outoftime(who: bool) -> void: func _on_turn_over() -> void: - stop_input = false - Log.debug("turn over. new turn: " + Globals.get_turn()) var matstr := mat2str() - Log.debug("matstr: " + matstr) + # Log.debug("matstr: " + matstr) if !matstr in history_matrixes: - Log.debug("new matrix entry") + # Log.debug("new matrix entry") history_matrixes[matstr] = 1 else: - Log.debug(["matrix entry = ", history_matrixes[matstr], "+ 1"]) + # 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 @@ -391,3 +392,59 @@ func _on_turn_over() -> void: drawed("stalemate") elif threefoldrepetition() >= 3: drawed("threefold repetition") + + +func play_pgn(pgn: String, instant := false): + for san in Pgn.parse(pgn).moves: + play_san(san) # 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") + + +func play_san(san: String, instant := false) -> void: + stop_input = true + 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 @@ -2,7 +2,7 @@ extends Node class_name PGN -func parse(string): +func parse(string) -> Dictionary: # put tags into a dictionary, # and the moves into a array @@ -17,13 +17,15 @@ func parse(string): var headers := {} var lines = Array(string.split("\n")) while !lines.empty(): - var line = lines.pop_front().strip_edges() + var line = lines[0].strip_edges() if !line or line[0] in ["%", ";"]: + lines.pop_front() continue if line[0] != "[": break + lines.pop_front() var tag_match = tagex.search(line) if tag_match: var cap = tag_match.strings @@ -32,7 +34,7 @@ func parse(string): else: # invalid headers push_error("invalid headers") - return + return {} else: break @@ -44,30 +46,6 @@ func parse(string): if line[0] in ["%", ";"]: continue for found in movetextex.search_all(line): - movetext.append(found.strings[1]) - - return [headers, movetext] - - -func _ready(): - var parsed = parse( - """ - [Event \"F/S Return Match\"] - [Site \"Belgrade, Serbia JUG\"] - [Date \"1992.11.04\"] - [Round \"29\"] - [White \"Fischer, Robert J.\"] - [Black \"Spassky, Boris V.\"] - [Result \"1/2-1/2\"] - - 1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 {This opening is called the Ruy Lopez.} - 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7 - 11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 b4 15. Nb1 h6 16. Bh4 c5 17. dxe5 - Nxe4 18. Bxe7 Qxe7 19. exd6 Qf6 20. Nbd2 Nxd6 21. Nc4 Nxc4 22. Bxc4 Nb6 - 23. Ne5 Rae8 24. Bxf7+ Rxf7 25. Nxf7 Rxe1+ 26. Qxe1 Kxf7 27. Qe3 Qg5 28. Qxg5 - hxg5 29. b3 Ke6 30. a3 Kd6 31. axb4 cxb4 32. Ra5 Nd5 33. f3 Bc8 34. Kf2 Bf5 - 35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5 40. Rd6 Kc5 41. Ra6 - Nf2 42. g4 Bd3 43. Re6 1/2-1/2 - """ - ) - print(parsed) + if found.strings[1]: + movetext.append(found.strings[1]) + return {"headers": headers, "moves": movetext} diff --git a/PGN/test_pgns.gd b/PGN/test_pgns.gd new file mode 100644 index 0000000..d46d4ff --- /dev/null +++ b/PGN/test_pgns.gd @@ -0,0 +1,26 @@ +extends VBoxContainer + +export(PoolStringArray) var pgns = [] + +var in_sim = false + + +func _ready(): + if !Debug.debug: + queue_free() + + +func _load(i: int): + in_sim = true + var boar = load("res://Board.tscn").instance() + get_tree().get_root().add_child(boar) + boar.get_node("Grid").play_pgn(pgns[i]) + get_parent().hide() + + +func _input(_event): + if Input.is_action_pressed("ui_cancel") and in_sim: + in_sim = false + get_node("/root/Board").queue_free() + get_parent().show() + Globals.reset_vars() diff --git a/PGN/test_pgns.tscn b/PGN/test_pgns.tscn new file mode 100644 index 0000000..a73b96e --- /dev/null +++ b/PGN/test_pgns.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://ui/theme/main.tres" type="Theme" id=1] +[ext_resource path="res://PGN/test_pgns.gd" type="Script" id=2] + +[node name="tests" type="VBoxContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +theme = ExtResource( 1 ) +script = ExtResource( 2 ) +pgns = PoolStringArray( "1.Nf3 Nf6 2.c4 g6 3.Nc3 Bg7 4.d4 O-O 5.Bf4 d5 6.Qb3 dxc4 7.Qxc4 c6 8.e4 Nbd7 9.Rd1 Nb6 10.Qc5 Bg4 11.Bg5 Na4 12.Qa3 Nxc3 13.bxc3 Nxe4 14.Bxe7 Qb6 15.Bc4 Nxc3 16.Bc5 Rfe8+ 17.Kf1 Be6 18.Bxb6 Bxc4+ 19.Kg1 Ne2+ 20.Kf1 Nxd4+ 21.Kg1 Ne2+ 22.Kf1 Nc3+ 23.Kg1 axb6 24.Qb4 Ra4 25.Qxb6 Nxd1 26.h3 Rxa2 27.Kh2 Nxf2 28.Re1 Rxe1 29.Qd8+ Bf8 30.Nxe1 Bd5 31.Nf3 Ne4 32.Qb8 b5 33.h4 h5 34.Ne5 Kg7 35.Kg1 Bc5+ 36.Kf1 Ng3+ 37.Ke1 Bb4+ 38.Kd1 Bb3+ 39.Kc1 Ne2+ 40.Kb1 Nc3+ 41.Kc1 Rc2# 0-1" ) + +[node name="gameofthecentury" type="Button" parent="."] +margin_left = 454.0 +margin_right = 967.0 +margin_bottom = 106.0 +size_flags_horizontal = 4 +text = "game of the century" + +[connection signal="pressed" from="gameofthecentury" to="." method="_load" binds= [ 0 ]] @@ -1,3 +1,8 @@ - +[](https://github.com/bend-n/chess/blob/main/icon.png "Icon") +[](https://github.com/bend-n/chess "Go to GitHub repo") +[](https://github.com/bend-n/chess/actions/workflows/export.yml "Build workflow") +[](https://github.com/bend-n/chess/releases/ "GitHub releases") +[](https://github.com/bend-n/chess/blob/main/LICENSE "License") +[](https://github.com/bend-n/chess/issues "Issues") completly works (real) diff --git a/SanParse/Move.gd b/SanParse/Move.gd index f5e40d8..afa07a9 100644 --- a/SanParse/Move.gd +++ b/SanParse/Move.gd @@ -61,6 +61,8 @@ func compile() -> String: # compiles the structure to a san # print(Utils.to_algebraic(make_long(SanParse.parse("N1xc3").move_kind.data, false, SanParser.KNIGHT)[0]) == "b1") ### fix short san func make_long(): + if move_kind.type == MoveKind.CASTLE: + return var newvecs: PoolVector2Array = [] var vectors = move_kind.data @@ -85,21 +87,25 @@ func long_helper(vec: Vector2, attack: bool, move: bool, touch: Vector2): if vec.y == -1 and vec.x != -1: for y in range(8): var spot = Piece.at_pos(Vector2(vec.x, y)) - if Utils.spotispiece(piece, spot) and spot.can_touch(touch, attack, move): + if long_helper_helper(spot, touch, attack, move): return Vector2(vec.x, y) elif vec.x == -1 and vec.y != -1: for x in range(8): var spot = Piece.at_pos(Vector2(x, vec.y)) - if Utils.spotispiece(piece, spot) and spot.can_touch(touch, attack, move): + if long_helper_helper(spot, touch, attack, move): return Vector2(x, vec.y) elif vec == Vector2(-1, -1): for x in range(8): for y in range(8): var spot = Piece.at_pos(Vector2(x, y)) - if Utils.spotispiece(piece, spot) and spot.can_touch(touch, attack, move): + if long_helper_helper(spot, touch, attack, move): return Vector2(x, y) +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) + + class MoveKind: extends Resource enum CASTLETYPES { NONE, QUEEN_SIDE, KING_SIDE } diff --git a/SanParse/SanParse.gd b/SanParse/SanParse.gd index 9a1f130..a93b430 100644 --- a/SanParse/SanParse.gd +++ b/SanParse/SanParse.gd @@ -112,8 +112,8 @@ func regexmatch(san: String) -> Move: re = regexs.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) - mov.set_check_type(cap[5]) + var mov = Move.new(from_str(cap[1]), [UNKNOWN_POS, pos(cap[2], cap[3])], true) + mov.set_check_type(cap[4]) return mov re = regexs.specific_column_piece_capture.search(san) @@ -65,6 +65,15 @@ func get_fen() -> String: ) # pos # turn # castling # enpassant # halfmove # fullmove +static func str_bool(string: String) -> bool: + string = string.to_lower() + if string == "true": + return true + if string == "false": + return false + return false + + func add_move(move: String) -> void: if Globals.turn == false: moves_list.append("%s. %s" % [Globals.fullmove, move]) @@ -174,11 +183,7 @@ 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 get_tree().get_root().has_node("Board"): - Globals.network.stopgame("quit") - yield(get_tree(), "idle_frame") # wait for the packet to send Log.debug("Bye!") - get_tree().quit() static func to_algebraic(pos: Vector2) -> String: diff --git a/assets/ui/circle.png b/assets/ui/circle.png new file mode 100644 index 0000000..c871f5d --- /dev/null +++ b/assets/ui/circle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb308529d4020815eafa35781958ebae9108d3a5ff3b538b90acd7beb0f9ddc9 +size 3529 diff --git a/assets/ui/circle.png.import b/assets/ui/circle.png.import new file mode 100644 index 0000000..e7344bf --- /dev/null +++ b/assets/ui/circle.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/circle.png-56c8a136dc4ef80f0ed3434a9d801ecd.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/ui/circle.png" +dest_files=[ "res://.import/circle.png-56c8a136dc4ef80f0ed3434a9d801ecd.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/assets/ui/svg/circle.svg b/assets/ui/svg/circle.svg new file mode 100644 index 0000000..5a75abf --- /dev/null +++ b/assets/ui/svg/circle.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fd81ff2586de5ac118998c6baec1f644c7d4363751d1967a217204f995b1495 +size 1575 diff --git a/assets/ui/svg/circle_part.svg b/assets/ui/svg/circle_part.svg new file mode 100644 index 0000000..cc4127a --- /dev/null +++ b/assets/ui/svg/circle_part.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfbce10b6ce47cac5ee274572543cee7a42576ac52a8110ff9a626aa0296c095 +size 2045 diff --git a/export_presets.cfg b/export_presets.cfg index 0da1268..86fe2bc 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -52,7 +52,7 @@ variant/export_type=0 vram_texture_compression/for_desktop=true vram_texture_compression/for_mobile=false html/export_icon=true -html/custom_html_shell="" +html/custom_html_shell="res://html/custom.html" html/head_include="" html/canvas_resize_policy=2 html/focus_canvas_on_start=true diff --git a/html/custom.html b/html/custom.html new file mode 100644 index 0000000..b17b095 --- /dev/null +++ b/html/custom.html @@ -0,0 +1,280 @@ +<!DOCTYPE html> +<html xmlns="https://www.w3.org/1999/xhtml" lang="" xml:lang=""> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, user-scalable=no" /> + <title>$GODOT_PROJECT_NAME</title> + <style type="text/css"> + body { + touch-action: none; + margin: 0; + border: 0 none; + padding: 0; + min-width: 100%; + min-height: 100%; + text-align: center; + background-size: cover; + background-repeat: no-repeat; + background: linear-gradient(to right, yellow, orange 60%, cyan); + } + + #canvas { + display: block; + margin: 0; + color: white; + } + + #canvas:focus { + outline: none; + } + + .godot { + font-family: verdana; + font-weight: bold; + color: #e0e0e0; + background-color: black; + } + + /* Status display + * ============== */ + + #status { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + /* don't consume click events - make children visible explicitly */ + visibility: hidden; + } + + #status-progress { + width: 600px; + height: 50px; + background-color: #dcc621; + background-image: linear-gradient(to left, cyan, orange 60%, yellow); + border: 5px solid black; + padding: 3px; + border-radius: 30px; + visibility: visible; + } + + @media only screen and (orientation: portrait) { + #status-progress { + width: 61.8%; + } + } + + #status-progress-inner { + height: 100%; + width: 0; + box-sizing: border-box; + transition: width 0.5s linear; + background-image: linear-gradient(45deg, blue 30%, red); + border: 3px solid black; + border-radius: 30px; + } + + #status-indeterminate { + height: 42px; + visibility: visible; + position: relative; + } + + #status-indeterminate > div { + width: 4.5px; + height: 0; + border-style: solid; + border-width: 9px 3px 0 3px; + border-color: black transparent transparent transparent; + transform-origin: center 21px; + position: absolute; + } + + #status-indeterminate > div:nth-child(1) { + transform: rotate(22.5deg); + } + #status-indeterminate > div:nth-child(2) { + transform: rotate(67.5deg); + } + #status-indeterminate > div:nth-child(3) { + transform: rotate(112.5deg); + } + #status-indeterminate > div:nth-child(4) { + transform: rotate(157.5deg); + } + #status-indeterminate > div:nth-child(5) { + transform: rotate(202.5deg); + } + #status-indeterminate > div:nth-child(6) { + transform: rotate(247.5deg); + } + #status-indeterminate > div:nth-child(7) { + transform: rotate(292.5deg); + } + #status-indeterminate > div:nth-child(8) { + transform: rotate(337.5deg); + } + + #status-notice { + margin: 0 100px; + line-height: 1.3; + visibility: visible; + padding: 4px 6px; + visibility: visible; + } + </style> + $GODOT_HEAD_INCLUDE + </head> + <body> + <canvas id="canvas"> + HTML5 canvas appears to be unsupported in the current browser.<br /> + Please try updating or use a different browser. + </canvas> + <div id="status"> + <div + id="status-progress" + style="display: none" + oncontextmenu="event.preventDefault();" + > + <div id="status-progress-inner"></div> + </div> + <div + id="status-indeterminate" + style="display: none" + oncontextmenu="event.preventDefault();" + > + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + </div> + <div id="status-notice" class="godot" style="display: none"></div> + </div> + + <script type="text/javascript" src="$GODOT_URL"></script> + <script type="text/javascript"> + //<![CDATA[ + + const GODOT_CONFIG = $GODOT_CONFIG; + var engine = new Engine(GODOT_CONFIG); + + (function () { + const INDETERMINATE_STATUS_STEP_MS = 100; + var statusProgress = document.getElementById("status-progress"); + var statusProgressInner = document.getElementById( + "status-progress-inner" + ); + var statusIndeterminate = document.getElementById( + "status-indeterminate" + ); + var statusNotice = document.getElementById("status-notice"); + + var initializing = true; + var statusMode = "hidden"; + + var animationCallbacks = []; + function animate(time) { + animationCallbacks.forEach((callback) => callback(time)); + requestAnimationFrame(animate); + } + requestAnimationFrame(animate); + + function setStatusMode(mode) { + if (statusMode === mode || !initializing) return; + [statusProgress, statusIndeterminate, statusNotice].forEach( + (elem) => { + elem.style.display = "none"; + } + ); + animationCallbacks = animationCallbacks.filter(function (value) { + return value != animateStatusIndeterminate; + }); + switch (mode) { + case "progress": + statusProgress.style.display = "block"; + break; + case "indeterminate": + statusIndeterminate.style.display = "block"; + animationCallbacks.push(animateStatusIndeterminate); + break; + case "notice": + statusNotice.style.display = "block"; + break; + case "hidden": + break; + default: + throw new Error("Invalid status mode"); + } + statusMode = mode; + } + + function animateStatusIndeterminate(ms) { + var i = Math.floor((ms / INDETERMINATE_STATUS_STEP_MS) % 8); + if (statusIndeterminate.children[i].style.borderTopColor == "") { + Array.prototype.slice + .call(statusIndeterminate.children) + .forEach((child) => { + child.style.borderTopColor = ""; + }); + statusIndeterminate.children[i].style.borderTopColor = "#dfdfdf"; + } + } + + function setStatusNotice(text) { + while (statusNotice.lastChild) { + statusNotice.removeChild(statusNotice.lastChild); + } + var lines = text.split("\n"); + lines.forEach((line) => { + statusNotice.appendChild(document.createTextNode(line)); + statusNotice.appendChild(document.createElement("br")); + }); + } + + function displayFailureNotice(err) { + var msg = err.message || err; + console.error(msg); + setStatusNotice(msg); + setStatusMode("notice"); + initializing = false; + } + + if (!Engine.isWebGLAvailable()) { + displayFailureNotice("WebGL not available"); + } else { + setStatusMode("indeterminate"); + engine + .startGame({ + onProgress: function (current, total) { + if (total > 0) { + statusProgressInner.style.width = + (current / total) * 100 + "%"; + setStatusMode("progress"); + if (current === total) { + // wait for progress bar animation + setTimeout(() => { + setStatusMode("indeterminate"); + }, 500); + } + } else { + setStatusMode("indeterminate"); + } + }, + }) + .then(() => { + setStatusMode("hidden"); + initializing = false; + }, displayFailureNotice); + } + })(); + </script> + </body> +</html> diff --git a/networking/Network.gd b/networking/Network.gd index 10a21e8..76df375 100644 --- a/networking/Network.gd +++ b/networking/Network.gd @@ -10,11 +10,11 @@ const HEADERS := { "joinrequest": "J", "hostrequest": "H", "stopgame": "K", - "ping": "P", "signup": "C", "signin": ">", "relay": "R", # relay goes to both "signal": "S", # signal is one way + "loadpgn": "L" # server telling me to load a pgn } const MOVEHEADERS := { # subheaders for HEADERS.move @@ -25,7 +25,7 @@ const MOVEHEADERS := { # subheaders for HEADERS.move "promote": "Q", } -const RELAYHEADERS := {"chat": "C", "startgame": "S"} +const RELAYHEADERS := {"chat": "C"} const SIGNALHEADERS := {"takeback": "T", "draw": "D", "resign": "R"} # subheaders for HEADERS.signal var notation := "" @@ -38,7 +38,7 @@ signal game_over(problem, isok) signal connection_established signal signal_recieved(what) -## for accounts +## for accounts(mostly) signal signinresult(what) signal signupresult(what) @@ -52,11 +52,6 @@ func _ready() -> void: ws.connect("connection_error", self, "_connection_error") ws.connect("data_received", self, "_data_recieved") ws.connect_to_url(url) - var t := Timer.new() - add_child(t) - t.wait_time = 1 - t.start(1) - t.connect("timeout", self, "ping") func signin(data): @@ -67,10 +62,6 @@ func signup(data): send_packet(data, HEADERS.signup) -func ping() -> void: - send_packet("ping", HEADERS.ping) - - func close() -> void: ws.disconnect_from_host(0, "Close") @@ -100,6 +91,14 @@ func signal(body, header: String, keyname := "body", _mainheader := HEADERS.sign return data +func join_game(game: String) -> void: + send_packet({"gamecode": game, "id": SaveLoad.files.id.data.id}, HEADERS.joinrequest) + + +func host_game(game: String) -> void: + send_packet({"gamecode": game, "id": SaveLoad.files.id.data.id}, HEADERS.hostrequest) + + func relay_signal(body, header: String, keyname := "body") -> Dictionary: # its really the same thing as signal() return signal(body, header, keyname, HEADERS.relay) @@ -124,12 +123,13 @@ func _data_recieved() -> void: var relay: Dictionary = text if relay.type in MOVEHEADERS.values(): emit_signal("move_data", text.move) - else: - match relay.type: - RELAYHEADERS.startgame: - emit_signal("start_game") HEADERS.joinrequest: emit_signal("join_result", text) + HEADERS.loadpgn: + emit_signal("start_game") + yield(get_tree().create_timer(.5), "timeout") + Log.info("load pgn " + text) + Globals.grid.play_pgn(text, true) # call deferred wont work since grid obj may be null HEADERS.stopgame: if !PacketHandler.leaving: # dont emit the signal if its a stophost thing (HACK) emit_signal("game_over", text, true) @@ -137,8 +137,6 @@ func _data_recieved() -> void: HEADERS.signal: var signal: Dictionary = text emit_signal("signal_recieved", signal) - HEADERS.ping: - pass HEADERS.signup: emit_signal("signupresult", text) HEADERS.signin: @@ -156,3 +154,5 @@ func _process(_delta: float) -> void: func send_packet(variant, header: String) -> void: if ws.get_peer(1).is_connected_to_host(): ws.get_peer(1).put_var({"header": header, "data": variant}) + else: + Log.err("not connected to server: packet %s not sent" % variant) diff --git a/networking/PacketHandler.gd b/networking/PacketHandler.gd index 28ee0e8..5529643 100644 --- a/networking/PacketHandler.gd +++ b/networking/PacketHandler.gd @@ -19,17 +19,15 @@ func return() -> void: # return to the void if hosting: leaving = true Globals.network.stopgame("") # stop hosting + lobby.set_status("", true) set_hosting(false) lobby.set_buttons(true) - lobby.set_status("", true) func _ready() -> void: - get_tree().set_auto_accept_quit(false) if Utils.internet and get_tree().get_root().has_node("StartMenu"): var net := Network.new() Events.connect("go_back", self, "_handle_game_over") - net.connect("move_data", self, "_on_data") net.connect("join_result", self, "_on_join_result") net.connect("host_result", self, "_on_host_result") net.connect("game_over", self, "_handle_game_over") @@ -43,12 +41,12 @@ func _ready() -> void: func requestjoin() -> void: lobby.set_buttons(false) - Globals.network.send_packet(Globals.network.game_code, Globals.network.HEADERS.joinrequest) + Globals.network.join_game(Globals.network.game_code) func requesthost() -> void: lobby.set_buttons(false) - Globals.network.send_packet(Globals.network.game_code, Globals.network.HEADERS.hostrequest) + Globals.network.host_game(Globals.network.game_code) func network_ready() -> void: @@ -57,8 +55,7 @@ func network_ready() -> void: func _on_join_result(accepted: String) -> void: - if handle_result(accepted, "Joined!", false): - Globals.network.relay_signal("startgame", Network.RELAYHEADERS.startgame) + handle_result(accepted, "Joined!", false) func _on_host_result(accepted: String) -> void: @@ -78,8 +75,8 @@ func handle_result(accepted: String, resultstring: String, team := true) -> bool func _handle_game_over(error := "game over", isok := true) -> void: Globals.network.stopgame(error) Globals.reset_vars() - if get_tree().get_root().has_node("Board"): - get_tree().get_root().get_node("Board").queue_free() + if has_node("/root/Board"): + get_node("/root/Board").queue_free() lobby.set_status(error, isok) lobby.toggle(true) lobby.set_buttons(true) @@ -93,48 +90,3 @@ func _start_game() -> void: lobby.toggle(false) emit_signal("game_started") lobby.set_buttons(false) - - -func _on_data(data: String) -> void: - Globals.add_turn() - Log.debug([data, " recieved"]) - var san_to_add := data - var mov = SanParse.parse(data) - 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) - king.castle(king_goto) - Move.MoveKind.NORMAL: - # this handles promotion, taking, enpassant, and moves. - mov.make_long() - var positions = mov.move_kind.data - if mov.promotion != -1: # promotion part - Globals.grid.print_matrix_pretty(Globals.grid.matrix) - var promote_to = Utils.to_str(mov.promotion) - Piece.at_pos(positions[0]).promote_to(promote_to, mov.is_capture, positions[1]) - - elif mov.is_capture: # taking part - if Piece.at_pos(positions[1]): - Piece.at_pos(positions[0]).take(Piece.at_pos(positions[1])) - elif mov.piece == SanParser.PAWN: # enpassant part - var pawn: Pawn = Piece.at_pos(positions[0]) - pawn.passant(positions[1]) - san_to_add += " e.p." - else: # a very normal move - Piece.at_pos(positions[0]).moveto(positions[1]) - Utils.add_move(san_to_add) diff --git a/pieces/King.gd b/pieces/King.gd index 5411676..e0d56d7 100644 --- a/pieces/King.gd +++ b/pieces/King.gd @@ -75,7 +75,7 @@ func castleing(justcheckrooks := false) -> Array: return moves -func castle(position: Vector2) -> String: +func castle(position: Vector2, instant := false) -> String: var return_string := "" if can_castle.size() == 1: return_string = can_castle[0][3] @@ -85,7 +85,7 @@ func castle(position: Vector2) -> String: return_string = i[3] break can_castle.clear() - moveto(position, true) + moveto(position, instant) return return_string diff --git a/pieces/Pawn.gd b/pieces/Pawn.gd index f17bca3..0c44a70 100644 --- a/pieces/Pawn.gd +++ b/pieces/Pawn.gd @@ -40,19 +40,17 @@ func _exit_tree() -> void: Globals.pawns.remove(find) -func moveto(position: Vector2, real := true) -> void: +func moveto(position: Vector2, instant := false) -> void: # check if 2 step - if real: - 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 - .moveto(position, real) - if real: - Globals.reset_halfmove() + 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 + .moveto(position, instant) + Globals.reset_halfmove() func get_moves(_var := false, check_spots_check := true) -> PoolVector2Array: @@ -77,11 +75,11 @@ static func can_promote(position: Vector2) -> bool: return position.y >= 7 or position.y <= 0 -func passant(position: Vector2) -> void: +func passant(position: Vector2, instant := false) -> void: var to_take = position + Vector2(0, whiteint) - at_pos(to_take).took() + at_pos(to_take).took(instant) enpassant.resize(0) - moveto(position) + moveto(position, instant) func valid_to_passant_take(piece) -> bool: @@ -137,9 +135,9 @@ func promote(position: Vector2, type: String) -> void: sprites[i].show() -func promote_to(promote_to: String, is_capture: bool, position: Vector2): +func promote_to(promote_to: String, is_capture: bool, position: Vector2, instant := false): if is_capture and at_pos(position): - at_pos(position).took() + at_pos(position).took(instant) clear_clicked() Globals.grid.make_piece(position, piece(promote_to), white) took() diff --git a/pieces/Piece.gd b/pieces/Piece.gd index 07b4b4a..55d680d 100644 --- a/pieces/Piece.gd +++ b/pieces/Piece.gd @@ -57,8 +57,7 @@ func algebraic_move_notation(position: Vector2) -> String: func move(newpos: Vector2) -> void: # dont use directly; use moveto tween.interpolate_property(self, "position", position, newpos * Globals.grid.piece_size, 0.3, Tween.TRANS_BACK) - var signresult := sign(newpos.x * Globals.grid.piece_size.x - global_position.x) - Log.debug("signresult: " + str(signresult)) + var signresult := int(sign(newpos.x * Globals.grid.piece_size.x - global_position.x)) if signresult == 1: rotate.play("Right") elif signresult == -1: @@ -67,10 +66,10 @@ func move(newpos: Vector2) -> void: # dont use directly; use moveto tween.start() -func moveto(pos: Vector2, real := true) -> void: +func moveto(pos: Vector2, instant := false) -> void: Globals.grid.matrix[real_position.y][real_position.x] = null Globals.grid.matrix[pos.y][pos.x] = self - if real: + if !instant: real_position = pos move(real_position) SoundFx.play("Move") @@ -184,7 +183,8 @@ static func set_circle(positions: Array, type := "move") -> void: func checkcheck(pos) -> bool: # moves to position, then checks if your king is in check # TODO: figure out why this function isnt working with can_move() var mat: Array = Globals.grid.matrix.duplicate(true) # make a copy of the matrix - moveto(pos, false) # move to the position + Globals.grid.matrix[real_position.y][real_position.x] = null # remove the piece from the matrix + Globals.grid.matrix[pos.y][pos.x] = self # move the piece to the new position if Globals.grid.check_in_check(): # if you are still in check Globals.grid.matrix = mat # revert changes on the matrix return true @@ -198,17 +198,18 @@ static func is_on_board(vector: Vector2) -> bool: # limit the vector to the boa return true -func take(piece: Piece) -> void: +func take(piece: Piece, instant := false) -> void: clear_clicked() - piece.took() - moveto(piece.real_position, true) + piece.took(instant) + moveto(piece.real_position, instant) Globals.reset_halfmove() -func took() -> void: # called when piece is taken - SoundFx.play("Capture") +func took(instant := false) -> void: # called when piece is taken Globals.grid.matrix[real_position.y][real_position.x] = null - anim.play("Take") + if !instant: + SoundFx.play("Capture") + anim.play("Take") func set_frame(value := true) -> void: diff --git a/project.godot b/project.godot index 64e8663..e0b53df 100644 --- a/project.godot +++ b/project.godot @@ -79,11 +79,6 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://ui/Lobby.gd" }, { -"base": "Node", -"class": "Log", -"language": "GDScript", -"path": "res://ui/Log.gd" -}, { "base": "Resource", "class": "Move", "language": "GDScript", @@ -179,7 +174,6 @@ _global_script_class_icons={ "King": "res://assets/pieces/california/wK.png", "Knight": "res://assets/pieces/california/wN.png", "Lobby": "", -"Log": "", "Move": "", "NetManager": "", "Network": "", @@ -222,6 +216,7 @@ PacketHandler="*res://networking/PacketHandler.gd" Debug="*res://Debug.gd" SanParse="*res://SanParse/SanParse.gd" Pgn="*res://PGN/PGN.gd" +Log="*res://Log.gd" [debug] diff --git a/saveload.gd b/saveload.gd index 7d54b5f..455cc70 100644 --- a/saveload.gd +++ b/saveload.gd @@ -14,9 +14,11 @@ const default_settings_data = { "rainbow": true } +const default_id_data = {"id": "", "name": "", "country": "rainbow", "password": ""} + var files := { "settings": {"file": settings_file, "data": default_settings_data.duplicate(true)}, - "id": {"file": id, "data": {"id": "", "name": "", "country": "rainbow", "password": ""}} + "id": {"file": id, "data": default_id_data.duplicate()} } # file types @@ -65,9 +65,12 @@ class TestSan: func test_pawn_capture_long(): assert_capture("e3xd4", Vector2(4, 5), Vector2(3, 4), PAWN) - func test_piece_capture(): + func test_row_piece_capture(): assert_capture("R1xh3", Vector2(-1, 7), Vector2(7, 5), ROOK) + func test_piece_capture(): + assert_capture("Nxe4", Vector2(-1, -1), Vector2(4, 4), KNIGHT) + func test_piece_capture_file(): assert_capture("Rexh3", Vector2(4, -1), Vector2(7, 5), ROOK) @@ -97,6 +100,7 @@ class TestSan: test_piece_rank() test_piece_long() test_pawn_capture() + test_row_piece_capture() test_pawn_capture_promotion() test_pawn_capture_long() test_piece_capture() @@ -107,5 +111,5 @@ class TestSan: func _ready(): - if Debug.is_debug(): + if Debug.debug: TestSan.new() diff --git a/ui/Log.gd b/ui/Log.gd deleted file mode 100644 index 83e3e07..0000000 --- a/ui/Log.gd +++ /dev/null @@ -1,31 +0,0 @@ -extends Node -class_name Log - - -static func info(information) -> void: # logs the input string - print(to_str(information)) - - -static func debug(information) -> void: # logs the input string on debug builds - if OS.is_debug_build(): - print(to_str(information)) - - -static func err(information) -> void: # logs the input string to stderr - printerr(information) - - -static func to_str(arg) -> String: - if typeof(arg) == TYPE_ARRAY: - return arr2str(arg) - elif typeof(arg) == TYPE_STRING: - return arg - else: - return str(arg) - - -static func arr2str(arr: Array) -> String: - var string := "" - for i in arr: - string += str(i) + " " - return string diff --git a/ui/Settings.gd b/ui/Settings.gd index 24c148a..04fffa0 100644 --- a/ui/Settings.gd +++ b/ui/Settings.gd @@ -40,21 +40,17 @@ func update_button_visuals(set: Dictionary = settings) -> void: func _ready() -> void: if OS.has_feature("HTML5"): borderlessbutton.queue_free() - board_color1.color = settings.board_color1 - board_color2.color = settings.board_color2 for i in piece_sets: # add the items piece_set_button.add_icon_item(load("res://assets/pieces/" + i + "/wP.png"), i) piece_set_button.selected = Array(piece_sets).find(settings.piece_set) - Globals.piece_set = piece_sets[piece_set_button.selected] - Globals.board_color1 = settings.board_color1 - Globals.board_color2 = settings.board_color2 + update_vars() update_button_visuals() func update_vars() -> void: - Globals.piece_set = piece_sets[piece_set_button.selected] - Globals.board_color1 = board_color1.color - Globals.board_color2 = board_color2.color + Globals.piece_set = settings.piece_set + Globals.board_color1 = settings.board_color1 + Globals.board_color2 = settings.board_color2 OS.vsync_enabled = settings.vsync OS.window_fullscreen = settings.fullscreen OS.window_borderless = settings.borderless diff --git a/ui/StartMenu.tscn b/ui/StartMenu.tscn index 0f880c0..b0875cf 100644 --- a/ui/StartMenu.tscn +++ b/ui/StartMenu.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=9 format=2] +[gd_scene load_steps=10 format=2] [ext_resource path="res://ui/theme/main.tres" type="Theme" id=1] [ext_resource path="res://ui/StartMenu.gd" type="Script" id=2] @@ -7,6 +7,7 @@ [ext_resource path="res://assets/ui/verdana-bold.ttf" type="DynamicFontData" id=5] [ext_resource path="res://ui/account/Account.tscn" type="PackedScene" id=6] [ext_resource path="res://test.gd" type="Script" id=7] +[ext_resource path="res://PGN/test_pgns.tscn" type="PackedScene" id=8] [sub_resource type="DynamicFont" id=1] size = 400 @@ -56,6 +57,9 @@ margin_top = 90.0 margin_right = -30.0 margin_bottom = -30.0 +[node name="tests" parent="tabs" instance=ExtResource( 8 )] +visible = false + [node name="quit" type="Button" parent="tabs"] visible = false anchor_right = 1.0 diff --git a/ui/account/Account.gd b/ui/account/Account.gd index 5ee24cf..19383a0 100644 --- a/ui/account/Account.gd +++ b/ui/account/Account.gd @@ -4,11 +4,14 @@ onready var flags: PoolStringArray = ["rainbow"] onready var flagchoice: OptionButton = $choose/signup/flag onready var data: Dictionary = SaveLoad.files.id.data onready var status: StatusLabel = $StatusLabel -onready var signup: UsernamePass = $choose/signup/usernamepass -onready var signin: UsernamePass = $choose/signin/usernamepass -var autologin = false +onready var tabs := { + "signup": $choose/signup/usernamepass, + "signin": $choose/signin/usernamepass, +} + var signed_in = false +var autologin = true func _ready(): @@ -21,40 +24,55 @@ func _ready(): func attempt_autologin(): - autologin = true if data.name and data.password: Globals.network.signin(data) - Log.info("Attempting autologin") - autologin = false + + +func _on_signin_pressed(): + $choose/signin/signinbutton.disabled = true + update_data(tabs.signin.username, tabs.signin.pw) + Globals.network.signin(data) func _on_signin_result(result): + var status_set = !autologin + autologin = false + $choose.show() $choose/signin/signinbutton.disabled = false if typeof(result) == TYPE_STRING: # ew, error, get it away from me - Log.err(result) - return + return reset(result, status_set) data.id = result.id data.country = result.country - save_data() - status.set_text("Sign in sucessfull!") - signed_in = true # yay + _after_result() + + +func _on_signup_pressed(): + $choose/signup/signupbutton.disabled = true + update_data(tabs.signup.username, tabs.signup.pw) + Globals.network.signup(data) func _on_signup_result(result: String): $choose/signup/signupbutton.disabled = false if "err:" in result: # ew error go awway - Log.err(result) - return + return reset(result) data.id = result + _after_result() + + +func reset(reason: String, set_status := true): + if set_status: + status.set_text(reason) + data = SaveLoad.default_id_data save_data() - status.set_text("Sign up sucessfull ( you are now logged in )!") - signed_in = true # yay + $choose.show() -func _on_signup_pressed(): - $choose/signup/signupbutton.disabled = true - update_data(signup.username, signup.pw) - Globals.network.signup(data) +func _after_result(): + save_data() + status.set_text("Signed in to " + SaveLoad.files.id.data.name) + signed_in = true # yay + $choose.hide() func update_data(username, pw): @@ -69,7 +87,7 @@ func save_data(): SaveLoad.save("id") -func _on_signin_pressed(): - $choose/signin/signinbutton.disabled = true - update_data(signin.username, signin.pw) - Globals.network.signin(data) +func _on_choose_tab_changed(tab: int): + var new: VBoxContainer = $choose.get_children()[tab].get_node("usernamepass") + var old = $choose.get_children()[1 if tab == 0 else 0].get_node("usernamepass") + new.update_data(old.export_data()) diff --git a/ui/account/Account.tscn b/ui/account/Account.tscn index ae0e072..04dba2c 100644 --- a/ui/account/Account.tscn +++ b/ui/account/Account.tscn @@ -41,6 +41,7 @@ margin_bottom = 140.0 script = ExtResource( 7 ) [node name="choose" type="TabContainer" parent="."] +visible = false margin_left = 177.0 margin_top = 155.0 margin_right = 1245.0 @@ -116,5 +117,6 @@ size_flags_horizontal = 4 enabled_focus_mode = 0 text = "sign in" +[connection signal="tab_changed" from="choose" to="." method="_on_choose_tab_changed"] [connection signal="pressed" from="choose/signup/signupbutton" to="." method="_on_signup_pressed"] [connection signal="pressed" from="choose/signin/signinbutton" to="." method="_on_signin_pressed"] diff --git a/ui/account/usernamepass.gd b/ui/account/usernamepass.gd index 387b806..851152f 100644 --- a/ui/account/usernamepass.gd +++ b/ui/account/usernamepass.gd @@ -3,3 +3,24 @@ class_name UsernamePass onready var username = $Username onready var pw = $H/Password + + +func update_data(data: Dictionary) -> void: + username.text = data.user + username.caret_position = data.user_caret + pw.text = data.pasw + pw.caret_position = data.pasw_caret + + +func export_data() -> Dictionary: + return { + "user": username.text, + "user_caret": username.caret_position, + "pasw": pw.text, + "pasw_caret": pw.caret_position + } + + +func set_enabled(enabled: bool) -> void: + username.editable = enabled + pw.editable = enabled diff --git a/ui/animations/LoadingAnimation.tscn b/ui/animations/LoadingAnimation.tscn new file mode 100644 index 0000000..b92cfc5 --- /dev/null +++ b/ui/animations/LoadingAnimation.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://assets/ui/whitespace.png" type="Texture" id=1] + +[node name="LoadingAnimation" type="TextureRect"] +anchor_right = 1.0 +anchor_bottom = 1.0 +texture = ExtResource( 1 ) +expand = true |