online multiplayer chess game (note server currently down)
42 files changed, 1181 insertions, 155 deletions
@@ -4,3 +4,5 @@ logs/ *.py .vscode/ exports/ +# for later +assets/flags/ @@ -52,7 +52,7 @@ func _draw() -> void: return draw_style_box(style, calculate_size()) var i = len(refs) - while i >= 0: + while i > 0: i -= 1 var pos := Vector2(offset.x, (i + 1) * vertical) draw_string(font, pos, get_string(refs[i])) @@ -41,10 +41,6 @@ func pack_vars() -> Dictionary: } -func turns() -> int: - return fullmove - - func reset_halfmove() -> void: halfmove = 0 __nosethalfmove = true @@ -77,13 +77,10 @@ static func print_matrix_pretty(mat: Array) -> void: # print the matrix print(topper_header) # print the top border else: print(middle_header) # print the middle border - var row := "┃ %s ┃ " % str(8 - j) # init the string + var row := "%s%s %s " % [ender, 8 - j, ender] # init the string for i in range(8): # for each column var c: Piece = r[i] # get the column - if c: # if there is a piece - row += c.mininame + ender # add the shortname - else: # if there is no piece - row += " " + ender + row += "%s%s" % [c.mininame, ender] if c else " " + ender # add the piece print(row) # print the string print("%s\n%s\n%s" % [middish_heads, letter_header, smaller_heads]) @@ -146,9 +143,7 @@ func init_label(labelscene: PackedScene, i: int, position: Vector2, text: String func threefoldrepetition() -> int: - if !history_matrixes.values(): - return 0 - return history_matrixes.values().max() + return 0 if !history_matrixes.values() else history_matrixes.values().max() func mat2str(mat: Array = matrix) -> String: @@ -156,13 +151,9 @@ func mat2str(mat: Array = matrix) -> String: for y in range(8): for x in range(8): var spot: Piece = mat[y][x] - if spot: - string += spot.mininame - else: - string += "*" - for i in mat[8].keys(): # store the metadata - var thing = mat[8][i] - string += i + str(thing) + string += spot.mininame if spot else "*" + for key in mat[8].keys(): # store the metadata + string += "%s:%s" % [key, mat[8][key]] return string @@ -177,7 +168,7 @@ func drawed(reason := "") -> void: func win(winner: bool, reason := "") -> void: ui.set_status("%s won the game by %s" % ["white" if winner else "black", reason], 0) # black won the game by checkmate Events.emit_signal("game_over") - Log.info("%s won the game in %s turns!" % ["white" if winner else "black", Globals.turns()]) + Log.info("%s won the game in %s turns!" % ["white" if winner else "black", Globals.fullmove]) SoundFx.play("Victory") yield(get_tree().create_timer(5), "timeout") Events.emit_signal("go_back") @@ -189,12 +180,11 @@ func check_in_check(prin := false) -> bool: # check if in_check 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 - # control never flows here if prin: + # control never flows here Globals.in_check = true # set in_check Globals.checking_piece = spot # set checking_piece SoundFx.play("Check") - Log.info("check") return true # stop at the first check found return false @@ -2,16 +2,16 @@ extends Node static func info(information) -> void: # logs the input string - print("[i] " + to_str(information)) + print("[i] %s" % to_str(information)) static func debug(information) -> void: # logs the input string on debug builds if Debug.debug: - print("[d] " + to_str(information)) + print("[d] %s" % to_str(information)) static func err(information) -> void: # logs the input string to stderr - printerr("[E]" + to_str(information)) + printerr("[E] %s" % to_str(information)) static func to_str(arg) -> String: @@ -23,5 +23,5 @@ static func to_str(arg) -> String: static func arr2str(arr: Array) -> String: var string := "" for i in arr: - string += str(i) + " " + string += "%s " % i return string diff --git a/PGN/PGN.gd b/PGN/PGN.gd new file mode 100644 index 0000000..32ca121 --- /dev/null +++ b/PGN/PGN.gd @@ -0,0 +1,73 @@ +extends Node +class_name PGN + + +func parse(string): + # put tags into a dictionary, + # and the moves into a array + + var tagex = SanParse.compile('^\\[([A-Za-z0-9_]+)\\s+"([^\\r]*)"\\]\\s*$', false) + var tagnameex = SanParse.compile("^[A-Za-z0-9_]+\\Z", false) + var movetextex = SanParse.compile( + "([NBKRQ]?[a-h]?[1-8]?[\\-x]?[a-h][1-8](?:=?[nbrqkNBRQK])?|[PNBRQK]?@[a-h][1-8]|--|Z0|0000|@@@@|O-O(?:-O)?|0-0(?:-0)?)|(\\{.*)|(;.*)|(\\$[0-9]+)|(\\()|(\\))|(\\*|1-0|0-1|1\\/2-1\\/2)|([\\?!]{1,2})", + false + ) + + # get headers + var headers := {} + var lines = Array(string.split("\n")) + while !lines.empty(): + var line = lines.pop_front().strip_edges() + if !line or line[0] in ["%", ";"]: + continue + + if line[0] != "[": + break + + var tag_match = tagex.search(line) + if tag_match: + var cap = tag_match.strings + if tagnameex.search(cap[1]): + headers[cap[1]] = cap[2] + else: + # invalid headers + push_error("invalid headers") + return + else: + break + + var movetext := PoolStringArray() + while !lines.empty(): + var line = lines.pop_front().strip_edges() + if !line: + break + 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) diff --git a/SanParse/Move.gd b/SanParse/Move.gd new file mode 100644 index 0000000..8abfca1 --- /dev/null +++ b/SanParse/Move.gd @@ -0,0 +1,73 @@ +class_name Move +extends Resource + +enum CASTLETYPES { NONE, QUEEN_SIDE, KING_SIDE } +enum CHECKTYPES { NONE, CHECK, CHECKMATE } +var generated_from := "" +var piece := 0 +var move_kind: MoveKind +var promotion := 0 +# var annotation := "" # later +var check_type := 0 +var is_capture := false + + +func _init(newpiece: int, newmove, capture := false) -> void: + piece = newpiece + is_capture = capture + move_kind = MoveKind.new(newmove) + + +static func castle_type(type: String) -> int: + return CASTLETYPES.QUEEN_SIDE if type == "O-O-O" else CASTLETYPES.KING_SIDE + + +func set_check_type(type: String) -> void: + match type: + "+": + check_type = CHECKTYPES.CHECK + "#": + check_type = CHECKTYPES.CHECKMATE + _: + check_type = CHECKTYPES.NONE + + +func compile() -> String: # compiles the structure to a san + var res := "" + match move_kind.type: + MoveKind.CASTLE: + res += move_kind.to_str() + MoveKind.NORMAL: + res += Utils.to_str(piece) + res += Utils.to_algebraic(move_kind.data[0]) + res = res + "x" if is_capture else res + res += Utils.to_algebraic(move_kind.data[1]) + res = res + "=" + Utils.to_str(promotion) if promotion != -1 else res + match check_type: + CHECKTYPES.CHECK: + res += "+" + CHECKTYPES.CHECKMATE: + res += "#" + _: + pass + return res + + +class MoveKind: + extends Resource + enum { NONE, NORMAL, CASTLE } + var type := 0 + var data # string OR array + + func _init(something): + if typeof(something) == TYPE_ARRAY and len(something) == 2: + type = NORMAL + data = PoolVector2Array(something) + elif typeof(something) == TYPE_INT: + type = CASTLE + data = something + else: + assert(false) + + func to_str() -> String: + return "O-O-O" if data == CASTLETYPES.QUEENSIDE else "O-O" diff --git a/SanParse/SanParse.gd b/SanParse/SanParse.gd new file mode 100644 index 0000000..3669672 --- /dev/null +++ b/SanParse/SanParse.gd @@ -0,0 +1,156 @@ +extends Node +class_name SanParser + +const end := "(\\+|\\#)?(\\?\\?|\\?|\\?!|!|!!)?$" # annotation + +var regexs := { + "pawn_move": compile("^([a-h])([1-8])"), + "long_pawn_move": compile("^([a-h])([1-8])([a-h])([1-8])"), # long-san + "piece_movement": compile("^([KQBNR])([a-h])([1-8])"), + "specific_row_piece_movement": compile("^([KQBNR])([0-9])([a-h])([1-8])"), + "specific_column_piece_movement": compile("^([KQBNR])([a-h])([a-h])([1-8])"), + "long_piece_movement": compile("^([KQBNR])([a-h])([0-9])([a-h])([1-8])"), + "pawn_capture": compile("^([a-h])x([a-h])([1-8])(?:=?([KQBNR]))?"), + "long_pawn_capture": compile("^([a-h])([1-8])x([a-h])([1-8])(?:=?([KQBNR]))?"), + "piece_capture": compile("^([KQBNR])x([a-h])([1-8])"), + "specific_column_piece_capture": compile("^([KQBNR])([a-h])x([a-h])([1-8])"), + "specific_row_piece_capture": compile("^([KQBNR])([0-9])x([a-h])([1-8])"), + "long_piece_capture": compile("^([KQBNR])([a-h])([0-9])x([a-h])([1-8])"), + "pawn_promotion": compile("^([a-h])([1-8])=?([KQBNR])"), + "castling": compile("^(O-O-O|O-O)"), +} + + +static func pos(col: String, row: String) -> Vector2: + return Utils.from_algebraic(col + row) + + +const UNKNOWN_POS = Vector2(-1, -1) +enum { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING } + + +func from_str(string: String) -> int: + var find = " NBRQK".find(string) + if find != -1: + return find + else: + return "PNBRQK".find(string) + + +func compile(regxstr: String, app_end := true) -> RegEx: #app_end because append end get it + var reg = RegEx.new() + reg.compile(regxstr + end if app_end else regxstr) + return reg + + +func parse(san: String) -> Move: + var mv = regexmatch(san) + mv.generated_from = san # for debugging i just moved the thing over so i can do this + return mv + + +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]) + 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]) + return mov + + re = regexs.piece_movement.search(san) + if re: + var cap = re.strings + var mov = Move.new(from_str(cap[1]), [UNKNOWN_POS, pos(cap[2], cap[3])]) + mov.set_check_type(cap[4]) + return mov + + 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])]) + 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])]) + mov.set_check_type(cap[5]) + return mov + + re = regexs.long_piece_movement.search(san) + if re: + var cap = re.strings + var mov = Move.new(from_str(cap[1]), [pos(cap[2], cap[3]), pos(cap[4], cap[5])]) + mov.set_check_type(cap[6]) + return mov + + re = regexs.pawn_capture.search(san) + if re: + var cap = re.strings + var mov = Move.new(PAWN, [Vector2(Utils.col_pos(cap[1]), -1), pos(cap[2], cap[3])], true) + mov.promotion = from_str(cap[4]) + mov.set_check_type(cap[5]) + return mov + + re = regexs.long_pawn_capture.search(san) + if re: + var cap = re.strings + var mov = Move.new(PAWN, [pos(cap[1], cap[2]), pos(cap[3], cap[4])], true) + mov.promotion = from_str(cap[5]) + mov.set_check_type(cap[6]) + return mov + + 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]) + return mov + + 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) + 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) + mov.set_check_type(cap[5]) + return mov + + re = regexs.long_piece_capture.search(san) + if re: + var cap = re.strings + var mov = Move.new(from_str(cap[1]), [pos(cap[2], cap[3]), pos(cap[4], cap[5])], true) + mov.set_check_type(cap[6]) + return mov + + re = regexs.pawn_promotion.search(san) + if re: + var cap = re.strings + var mov = Move.new(PAWN, [UNKNOWN_POS, pos(cap[1], cap[2])], true) + mov.promotion = from_str(cap[3]) + mov.set_check_type(cap[4]) + return mov + + re = regexs.castling.search(san) + if re: + var cap = re.strings + var mov = Move.new(KING, Move.castle_type(cap[1])) + mov.set_check_type(cap[2]) + return mov + + push_error("regex exhausted: no matches(%s)" % san) + + return null diff --git a/SanParse/test.gd b/SanParse/test.gd new file mode 100644 index 0000000..4c5bd5d --- /dev/null +++ b/SanParse/test.gd @@ -0,0 +1,109 @@ +extends Node + + +class TestSan: + extends SanParser + + func assert_castle(string: String, s): + var m = parse(string) + assert(m.move_kind.type == Move.MoveKind.CASTLE) + assert(m.move_kind.data == s) + assert(m.piece == KING) + assert(m.promotion == 0) + assert(m.check_type == Move.CHECKTYPES.NONE) + assert(m.is_capture == false) + + func assert_move(mv: String, start: Vector2, dest: Vector2, piece: int) -> void: + assert_all(parse(mv), PoolVector2Array([start, dest]), piece, false) + + 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: + 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: + assert(mv.promotion == promote) + + func test_algebraic_conversion(): + assert(Utils.from_algebraic(Utils.to_algebraic(Vector2.ZERO)) == Vector2.ZERO) + assert(Utils.from_algebraic(Utils.to_algebraic(Vector2.ONE)) == Vector2.ONE) + assert(pos("a", "9") == Utils.from_algebraic("a9")) + assert(Utils.col_pos("h") == 7) + assert(Utils.row_pos("1") == 7) + + func test_castle_short(): + assert_castle("O-O", Move.CASTLETYPES.KING_SIDE) + + func test_castle_long(): + assert_castle("O-O-O", Move.CASTLETYPES.QUEEN_SIDE) + + func test_pawn(): + assert_move("e4", UNKNOWN_POS, Vector2(4, 4), PAWN) + + func test_pawn_long(): + assert_move("e2e4", Vector2(4, 6), Vector2(4, 4), PAWN) + + func test_piece(): + assert_move("Qe4", UNKNOWN_POS, Vector2(4, 4), QUEEN) + + func test_piece_file(): + assert_move("Qbe4", Vector2(1, -1), Vector2(4, 4), QUEEN) + + func test_piece_rank(): + assert_move("Q1e4", Vector2(-1, 7), Vector2(4, 4), QUEEN) + + func test_piece_long(): + assert_move("Qb1e4", Vector2(1, 7), Vector2(4, 4), QUEEN) + + func test_pawn_capture(): + 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) + + func test_pawn_capture_long(): + assert_capture("e3xd4", Vector2(4, 5), Vector2(3, 4), PAWN) + + func test_piece_capture(): + assert_capture("R1xh3", Vector2(-1, 7), Vector2(7, 5), ROOK) + + func test_piece_capture_file(): + assert_capture("Rexh3", Vector2(4, -1), Vector2(7, 5), ROOK) + + func test_piece_capture_long(): + assert_capture("Re3xh3", Vector2(4, 5), Vector2(7, 5), ROOK) + + func test_pawn_promotion(): + assert_all(parse("d8=Q"), PoolVector2Array([UNKNOWN_POS, Vector2(3, 0)]), PAWN, QUEEN) + + func test_compile(): + var s = Move.new(PAWN, [Vector2(4, -1), Vector2(3, 0)]) + s.promotion = QUEEN + s.check_type = Move.CHECKTYPES.CHECK + s.is_capture = true + var result = s.compile() + assert(result == "exd8=Q+") + + func _init(): + test_algebraic_conversion() + test_castle_short() + test_castle_long() + test_pawn() + test_pawn_long() + test_piece() + test_piece_file() + test_piece_rank() + test_piece_long() + test_pawn_capture() + test_pawn_capture_promotion() + test_pawn_capture_long() + test_piece_capture() + test_piece_capture_file() + test_piece_capture_long() + test_pawn_promotion() + test_compile() + + +func _ready(): + TestSan.new() @@ -1,15 +1,74 @@ extends Node +var internet := false signal newmove(move) signal newfen(fen) -var turn_moves: PoolStringArray = [] -var turns_moves: PoolStringArray = [] -var internet := false -var counter := 0 +var moves_list: PoolStringArray = [] var fen := "" +func get_pgn(): + return moves_list.join(" ") + + +func _on_turn_over() -> void: + fen = get_fen() + Log.info("fen: " + fen) + emit_signal("newfen", fen) + + +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.twostepfirstmove 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 add_move(move: String) -> void: + if Globals.turn == false: + moves_list.append("%s. %s" % [Globals.fullmove, move]) + else: + moves_list.append(move) + emit_signal("newmove", move) + + func get_args() -> Dictionary: var arguments := {} for argument in OS.get_cmdline_args(): @@ -30,6 +89,7 @@ func _ready() -> void: print("run with command help to show this help") get_tree().quit() # dont wait Debug.monitor(self, "fen") + Debug.monitor(self, "pgn", "get_pgn()") static func exec_ext() -> String: @@ -52,26 +112,8 @@ static func is_king(inode) -> bool: return inode is King -func add_move(move: String) -> void: - if turn_moves.size() == 0: - turn_moves.append("%s. %s" % [Globals.fullmove, move]) - else: - turn_moves.append(move) - emit_signal("newmove", move) - - func reset_vars() -> void: - turn_moves.resize(0) - turns_moves.resize(0) - counter = 0 - - -static func to_algebraic(pos: Vector2) -> String: - return "abcdefgh"[pos.x] + str(round(8 - pos.y)) - - -static func from_algebraic(algebraic_position: String) -> Vector2: - return Vector2(ord(algebraic_position[0]) - ord("a"), 8 - int(algebraic_position[1])) + moves_list.resize(0) static func get_node_name(node: Node) -> Array: @@ -101,79 +143,31 @@ func internet_available() -> bool: return returnable -func walk_dir(path := "res://assets/pieces") -> PoolStringArray: # walk the directory, finding the asset packs - var folders: PoolStringArray = [] # init the folders +func walk_dir(path := "res://assets/pieces", only_dir := true, of_ext := "png", exclude := []) -> PoolStringArray: # walk the directory, finding the asset packs + var files := [] # init the files var dir := Directory.new() # init the directory if dir.open(path) == OK: # open the directory dir.list_dir_begin(true) # list the directory var file_name := dir.get_next() # get the next file while file_name != "": # while there is a file - if dir.current_is_dir(): # if the current is a directory - folders.append(file_name) # add the folder + if only_dir: + if dir.current_is_dir(): # if the current is a directory + files.append(file_name) # add the folder + else: + var split = file_name.split(".") + if split[-1] == of_ext and !split[0] in exclude: + files.append(split[0]) # add the file file_name = dir.get_next() # get the next file else: - Log.err("An error occurred when trying to access the path " + path) # print the error - return folders # return the folders + push_error("An error occurred when trying to access the path " + path) # print the error + files.sort() # sort the files + return PoolStringArray(files) # return the files func format_seconds(time: float, use_milliseconds: bool = false) -> String: return "%02d:%04.1f" if use_milliseconds else "%02d:%02d" % [time / 60, fmod(time, 60)] -func _on_turn_over() -> void: - fen = get_fen() - Log.info("fen: " + fen) - emit_signal("newfen", fen) - counter += 1 - if counter >= 2: - counter = 0 - turns_moves.append(turn_moves.join(" ")) - turn_moves.resize(0) - - -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.twostepfirstmove and pawn.just_set: - enpassants += 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 _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"): @@ -181,3 +175,25 @@ func _notification(what: int) -> void: yield(get_tree(), "idle_frame") # wait for the packet to send Log.debug("Bye!") get_tree().quit() + + +static func to_algebraic(pos: Vector2) -> String: + var column = "abcdefgh"[pos.x] if pos.x != -1 else "" + var row = str(round(8 - pos.y)) if pos.y != -1 else "" + return column + row + + +static func col_pos(col: String) -> int: + return "abcdefgh".find(col) + + +static func row_pos(row: String) -> int: + return 8 - int(row) + + +static func from_algebraic(pos: String) -> Vector2: + return Vector2(col_pos(pos[0]), row_pos(pos[1])) + + +static func to_str(type: int) -> String: + return " NBRQK"[type].strip_edges() # if its a pawn, return nothing diff --git a/assets/ui/eye.png b/assets/ui/eye.png new file mode 100644 index 0000000..52c248f --- /dev/null +++ b/assets/ui/eye.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:471e59bdc371b6634874605113a686beaac9088d8b1fc31f7882046f34a53338 +size 9722 diff --git a/assets/ui/eye.png.import b/assets/ui/eye.png.import new file mode 100644 index 0000000..c8a951c --- /dev/null +++ b/assets/ui/eye.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/eye.png-0766d2e31698a687d5f47fc9c0ab5dce.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/ui/eye.png" +dest_files=[ "res://.import/eye.png-0766d2e31698a687d5f47fc9c0ab5dce.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/eye_off.png b/assets/ui/eye_off.png new file mode 100644 index 0000000..26d8738 --- /dev/null +++ b/assets/ui/eye_off.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fc4a9b68b92d974c8f8b4a67f7f668d35bd6930c2d1e385f294b281f738d556 +size 10816 diff --git a/assets/ui/eye_off.png.import b/assets/ui/eye_off.png.import new file mode 100644 index 0000000..315311f --- /dev/null +++ b/assets/ui/eye_off.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/eye_off.png-c5b39170125b2d6fd97f423ccac6cd2a.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/ui/eye_off.png" +dest_files=[ "res://.import/eye_off.png-c5b39170125b2d6fd97f423ccac6cd2a.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/eyemap.png b/assets/ui/eyemap.png new file mode 100644 index 0000000..903165e --- /dev/null +++ b/assets/ui/eyemap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f1a48f12ac71c5d72663bd1a05b6588806cafbc7bf8ed694385ee02ca716213 +size 552 diff --git a/assets/ui/eyemap.png.import b/assets/ui/eyemap.png.import new file mode 100644 index 0000000..ad4e506 --- /dev/null +++ b/assets/ui/eyemap.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/eyemap.png-fa46d6ece12070f161a31275cd239843.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/ui/eyemap.png" +dest_files=[ "res://.import/eyemap.png-fa46d6ece12070f161a31275cd239843.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=false +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/eye.svg b/assets/ui/svg/eye.svg new file mode 100644 index 0000000..3dc7e34 --- /dev/null +++ b/assets/ui/svg/eye.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1608612fa07dbb57549659adf50718d30f6355f672ebeab9153239a3ddeffe71 +size 1676 diff --git a/assets/ui/svg/eye_off.svg b/assets/ui/svg/eye_off.svg new file mode 100644 index 0000000..9999dc8 --- /dev/null +++ b/assets/ui/svg/eye_off.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:daf53aa92e7d3843452bc3350953f3e3a9b5c830e5ed730ccd367229d6a9874b +size 1906 diff --git a/networking/Network.gd b/networking/Network.gd index 7ac4590..4633ef6 100644 --- a/networking/Network.gd +++ b/networking/Network.gd @@ -11,6 +11,8 @@ const HEADERS := { "hostrequest": "H", "stopgame": "K", "ping": "P", + "signup": "C", + "signin": ">", "relay": "R", # relay goes to both "signal": "S", # signal is one way } @@ -36,6 +38,10 @@ signal game_over(problem, isok) signal connection_established signal signal_recieved(what) +## for accounts +signal signinresult(what) +signal signupresult(what) + const url := "wss://gd-chess-server.herokuapp.com/" @@ -53,6 +59,14 @@ func _ready() -> void: t.connect("timeout", self, "ping") +func signin(data): + send_packet(data, HEADERS.signin) + + +func signup(data): + send_packet(data, HEADERS.signup) + + func ping() -> void: send_packet("ping", HEADERS.ping) @@ -99,8 +113,6 @@ func _data_recieved() -> void: var recieve: Dictionary = ws.get_peer(1).get_var() var header: String = recieve.header var text = recieve.data - if header != HEADERS.ping: - Log.debug("recieved %s of header %s" % [text, header]) match header: HEADERS.hostrequest: emit_signal("host_result", text) @@ -111,7 +123,6 @@ func _data_recieved() -> void: else: match relay.type: RELAYHEADERS.startgame: - print("Start") emit_signal("start_game") HEADERS.joinrequest: emit_signal("join_result", text) @@ -124,6 +135,10 @@ func _data_recieved() -> void: emit_signal("signal_recieved", signal) HEADERS.ping: pass + HEADERS.signup: + emit_signal("signupresult", text) + HEADERS.signin: + emit_signal("signinresult", text) _: Log.err("unknown header %s" % header) diff --git a/networking/PacketHandler.gd b/networking/PacketHandler.gd index 5034a4b..9cb6bbd 100644 --- a/networking/PacketHandler.gd +++ b/networking/PacketHandler.gd @@ -26,7 +26,7 @@ func return() -> void: # return to the void func _ready() -> void: get_tree().set_auto_accept_quit(false) - if Utils.internet: + 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") diff --git a/pieces/Piece.gd b/pieces/Piece.gd index 6d9b09f..299d3a9 100644 --- a/pieces/Piece.gd +++ b/pieces/Piece.gd @@ -161,7 +161,7 @@ func get_attacks(check_spots_check := true) -> PoolVector2Array: # @Override return final -func can_attack_piece(piece: Piece) -> bool: +func can_attack_piece(piece: Piece) -> bool:##i cant use pos in get_attacks for some bizarre reasons for pos in get_attacks(false): if at_pos(pos) == piece: return true diff --git a/project.godot b/project.godot index dcd2743..6b4aec9 100644 --- a/project.godot +++ b/project.godot @@ -84,6 +84,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://ui/Log.gd" }, { +"base": "Resource", +"class": "Move", +"language": "GDScript", +"path": "res://SanParse/Move.gd" +}, { "base": "Node", "class": "NetManager", "language": "GDScript", @@ -99,6 +104,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://ui/colorpicker/OldColorView.gd" }, { +"base": "Node", +"class": "PGN", +"language": "GDScript", +"path": "res://PGN/PGN.gd" +}, { "base": "Piece", "class": "Pawn", "language": "GDScript", @@ -124,12 +134,22 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://ui/barbutton/resignbutton.gd" }, { +"base": "LineEdit", +"class": "Restrict", +"language": "GDScript", +"path": "res://ui/account/Restrict.gd" +}, { "base": "Piece", "class": "Rook", "language": "GDScript", "path": "res://pieces/Rook.gd" }, { "base": "Node", +"class": "SanParser", +"language": "GDScript", +"path": "res://SanParse/SanParse.gd" +}, { +"base": "Node", "class": "SaveLoader", "language": "GDScript", "path": "res://saveload.gd" @@ -138,6 +158,11 @@ _global_script_classes=[ { "class": "StatusLabel", "language": "GDScript", "path": "res://ui/Status.gd" +}, { +"base": "Control", +"class": "UsernamePass", +"language": "GDScript", +"path": "res://ui/account/usernamepass.gd" } ] _global_script_class_icons={ "BarTextureButton": "", @@ -155,17 +180,22 @@ _global_script_class_icons={ "Knight": "res://assets/pieces/california/wN.png", "Lobby": "", "Log": "", +"Move": "", "NetManager": "", "Network": "", "OldColorView": "", +"PGN": "", "Pawn": "res://assets/pieces/california/wP.png", "Piece": "res://assets/pieces/california/wP.png", "Preview": "", "Queen": "res://assets/pieces/california/wQ.png", "ResignButton": "res://assets/ui/flag.png", +"Restrict": "", "Rook": "res://assets/pieces/california/wR.png", +"SanParser": "", "SaveLoader": "", -"StatusLabel": "" +"StatusLabel": "", +"UsernamePass": "" } [application] @@ -190,6 +220,8 @@ SaveLoad="*res://saveload.gd" ColorBack="*res://ui/background/ColorfullBackground.tscn" PacketHandler="*res://networking/PacketHandler.gd" Debug="*res://Debug.gd" +SanParse="*res://SanParse/SanParse.gd" +Pgn="*res://PGN/PGN.gd" [debug] diff --git a/saveload.gd b/saveload.gd index c77f97b..65866e1 100644 --- a/saveload.gd +++ b/saveload.gd @@ -2,6 +2,7 @@ extends Node class_name SaveLoader const settings_file := "user://chess.settings" +const id := "user://.chess.id" const default_settings_data = { "vsync": OS.vsync_enabled, @@ -12,28 +13,47 @@ const default_settings_data = { "board_color2": Color(0.54902, 0.635294, 0.678431) } -var files := {"settings": {"file": settings_file, "data": default_settings_data.duplicate(true)}} # file types +var files := { + "settings": {"file": settings_file, "data": default_settings_data.duplicate(true)}, + "id": {"file": id, "data": {"id": "", "name": "", "country": "rainbow", "password": ""}} +} # file types func _ready() -> void: + # Debug.monitor(self, "id data", "files.id.data") SaveLoad.load_data("settings") + SaveLoad.load_data("id") + + +func to_base64(variant) -> String: + return Marshalls.variant_to_base64(variant) + + +func from_base64(base64): + return Marshalls.base64_to_variant(base64) func save(type: String) -> void: var file := File.new() file.open(files[type]["file"], File.WRITE) - file.store_string(var2str(files[type]["data"])) + file.store_string(to_base64(files[type]["data"])) func load_data(type: String) -> Dictionary: if check_file(type): var file := File.new() file.open(files[type]["file"], File.READ) - if file.get_as_text().length() > 0: - var read_dictionary: Dictionary = str2var(file.get_as_text()) - if files[type]["data"].size() == read_dictionary.size(): + var text = file.get_as_text() + if len(text) > 0: + var read_dictionary = from_base64(text) + if typeof(read_dictionary) != TYPE_DICTIONARY: + save(type) # OVERWRITE + elif files[type]["data"].size() == read_dictionary.size(): files[type]["data"] = read_dictionary + save(type) # overwrite. file.close() + else: + save(type) return files[type]["data"] @@ -0,0 +1,109 @@ +extends Node + + +class TestSan: + extends SanParser + + func assert_castle(string: String, s): + var m = parse(string) + assert(m.move_kind.type == Move.MoveKind.CASTLE) + assert(m.move_kind.data == s) + assert(m.piece == KING) + assert(m.promotion == 0) + assert(m.check_type == Move.CHECKTYPES.NONE) + assert(m.is_capture == false) + + func assert_move(mv: String, start: Vector2, dest: Vector2, piece: int) -> void: + assert_all(parse(mv), PoolVector2Array([start, dest]), piece, false) + + 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: + 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: + assert(mv.promotion == promote) + + func test_algebraic_conversion(): + assert(Utils.from_algebraic(Utils.to_algebraic(Vector2.ZERO)) == Vector2.ZERO) + assert(Utils.from_algebraic(Utils.to_algebraic(Vector2.ONE)) == Vector2.ONE) + assert(pos("a", "9") == Utils.from_algebraic("a9")) + assert(Utils.col_pos("h") == 7) + assert(Utils.row_pos("1") == 7) + + func test_castle_short(): + assert_castle("O-O", Move.CASTLETYPES.KING_SIDE) + + func test_castle_long(): + assert_castle("O-O-O", Move.CASTLETYPES.QUEEN_SIDE) + + func test_pawn(): + assert_move("e4", UNKNOWN_POS, Vector2(4, 4), PAWN) + + func test_pawn_long(): + assert_move("e2e4", Vector2(4, 6), Vector2(4, 4), PAWN) + + func test_piece(): + assert_move("Qe4", UNKNOWN_POS, Vector2(4, 4), QUEEN) + + func test_piece_file(): + assert_move("Qbe4", Vector2(1, -1), Vector2(4, 4), QUEEN) + + func test_piece_rank(): + assert_move("Q1e4", Vector2(-1, 7), Vector2(4, 4), QUEEN) + + func test_piece_long(): + assert_move("Qb1e4", Vector2(1, 7), Vector2(4, 4), QUEEN) + + func test_pawn_capture(): + 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) + + func test_pawn_capture_long(): + assert_capture("e3xd4", Vector2(4, 5), Vector2(3, 4), PAWN) + + func test_piece_capture(): + assert_capture("R1xh3", Vector2(-1, 7), Vector2(7, 5), ROOK) + + func test_piece_capture_file(): + assert_capture("Rexh3", Vector2(4, -1), Vector2(7, 5), ROOK) + + func test_piece_capture_long(): + assert_capture("Re3xh3", Vector2(4, 5), Vector2(7, 5), ROOK) + + func test_pawn_promotion(): + assert_all(parse("d8=Q"), PoolVector2Array([UNKNOWN_POS, Vector2(3, 0)]), PAWN, QUEEN) + + func test_compile(): + var s = Move.new(PAWN, [Vector2(4, -1), Vector2(3, 0)]) + s.promotion = QUEEN + s.check_type = Move.CHECKTYPES.CHECK + s.is_capture = true + var result = s.compile() + assert(result == "exd8=Q+") + + func _init(): + test_algebraic_conversion() + test_castle_short() + test_castle_long() + test_pawn() + test_pawn_long() + test_piece() + test_piece_file() + test_piece_rank() + test_piece_long() + test_pawn_capture() + test_pawn_capture_promotion() + test_pawn_capture_long() + test_piece_capture() + test_piece_capture_file() + test_piece_capture_long() + test_pawn_promotion() + test_compile() + + +func _ready(): + TestSan.new() @@ -21,8 +21,7 @@ static func to_str(arg) -> String: elif typeof(arg) == TYPE_STRING: return arg else: - err("Called with invalid arguments") - return "" + return str(arg) static func arr2str(arr: Array) -> String: diff --git a/ui/Settings.tscn b/ui/Settings.tscn index f12eddd..3faf8e3 100644 --- a/ui/Settings.tscn +++ b/ui/Settings.tscn @@ -21,10 +21,9 @@ size_flags_vertical = 0 alignment = 1 [node name="TabContainer" type="TabContainer" parent="H"] -margin_left = 203.0 -margin_right = 703.0 +margin_left = 239.0 +margin_right = 668.0 margin_bottom = 570.0 -rect_min_size = Vector2( 500, 0 ) size_flags_vertical = 0 custom_constants/hseparation = 20 drag_to_rearrange_enabled = true @@ -40,25 +39,25 @@ margin_bottom = -30.0 alignment = 1 [node name="boardcolor1" parent="H/TabContainer/colors" instance=ExtResource( 5 )] -margin_left = 57.0 +margin_left = 21.0 margin_top = 26.0 -margin_right = 383.0 +margin_right = 347.0 margin_bottom = 132.0 size_flags_horizontal = 4 text = "boardcolor1" [node name="boardcolor2" parent="H/TabContainer/colors" instance=ExtResource( 5 )] -margin_left = 57.0 +margin_left = 21.0 margin_top = 147.0 -margin_right = 383.0 +margin_right = 347.0 margin_bottom = 253.0 size_flags_horizontal = 4 text = "boardcolor2" [node name="PieceSet" type="OptionButton" parent="H/TabContainer/colors"] -margin_left = 37.0 +margin_left = 1.0 margin_top = 268.0 -margin_right = 403.0 +margin_right = 367.0 margin_bottom = 424.0 focus_mode = 0 size_flags_horizontal = 4 @@ -72,15 +71,17 @@ icon = ExtResource( 3 ) visible = false anchor_right = 1.0 anchor_bottom = 1.0 -margin_top = 60.0 +margin_left = 30.0 +margin_top = 90.0 +margin_right = -30.0 +margin_bottom = -30.0 mouse_filter = 2 alignment = 1 [node name="VsyncButton" type="CheckBox" parent="H/TabContainer/window"] -margin_left = 113.0 -margin_top = 65.0 -margin_right = 386.0 -margin_bottom = 205.0 +margin_left = 213.0 +margin_right = 486.0 +margin_bottom = 140.0 focus_mode = 0 size_flags_horizontal = 4 pressed = true @@ -88,28 +89,28 @@ enabled_focus_mode = 0 text = "vsync" [node name="FullscreenButton" type="CheckBox" parent="H/TabContainer/window"] -margin_left = 65.0 -margin_top = 220.0 -margin_right = 434.0 -margin_bottom = 360.0 +margin_left = 165.0 +margin_top = 155.0 +margin_right = 534.0 +margin_bottom = 295.0 focus_mode = 0 size_flags_horizontal = 4 enabled_focus_mode = 0 text = "fullscreen" [node name="Borderless" type="CheckBox" parent="H/TabContainer/window"] -margin_left = 91.0 -margin_top = 375.0 -margin_right = 409.0 -margin_bottom = 515.0 +margin_left = 191.0 +margin_top = 310.0 +margin_right = 509.0 +margin_bottom = 450.0 focus_mode = 0 size_flags_horizontal = 4 enabled_focus_mode = 0 text = "borders" [node name="Container" type="VBoxContainer" parent="H"] -margin_left = 718.0 -margin_right = 1218.0 +margin_left = 683.0 +margin_right = 1183.0 margin_bottom = 800.0 rect_min_size = Vector2( 300, 0 ) diff --git a/ui/StartMenu.tscn b/ui/StartMenu.tscn index 1b69847..2d8a117 100644 --- a/ui/StartMenu.tscn +++ b/ui/StartMenu.tscn @@ -1,10 +1,12 @@ -[gd_scene load_steps=7 format=2] +[gd_scene load_steps=9 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] [ext_resource path="res://ui/Settings.tscn" type="PackedScene" id=3] [ext_resource path="res://ui/Lobby.tscn" type="PackedScene" id=4] [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://SanParse/test.gd" type="Script" id=7] [sub_resource type="DynamicFont" id=1] size = 400 @@ -47,6 +49,13 @@ margin_top = 90.0 margin_right = -30.0 margin_bottom = -30.0 +[node name="account" parent="tabs" instance=ExtResource( 6 )] +visible = false +margin_left = 30.0 +margin_top = 90.0 +margin_right = -30.0 +margin_bottom = -30.0 + [node name="quit" type="Button" parent="tabs"] visible = false anchor_right = 1.0 @@ -62,5 +71,8 @@ custom_fonts/font = SubResource( 1 ) enabled_focus_mode = 0 text = "exit" +[node name="Node2D" type="Node2D" parent="."] +script = ExtResource( 7 ) + [connection signal="tab_changed" from="tabs" to="tabs/Lobby" method="_on_tabs_tab_changed"] [connection signal="pressed" from="tabs/quit" to="." method="_on_quit_pressed"] diff --git a/ui/Status.gd b/ui/Status.gd index 351f523..99c6697 100644 --- a/ui/Status.gd +++ b/ui/Status.gd @@ -3,7 +3,6 @@ class_name StatusLabel func set_text(newtext: String, time := 7) -> void: - print("set to ", newtext) text = newtext if time != 0: yield(get_tree().create_timer(time), "timeout") diff --git a/ui/TimerLabels.gd b/ui/TimerLabels.gd index a8d8b7c..59e3374 100644 --- a/ui/TimerLabels.gd +++ b/ui/TimerLabels.gd @@ -38,6 +38,6 @@ 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/account/Account.gd b/ui/account/Account.gd new file mode 100644 index 0000000..5ee24cf --- /dev/null +++ b/ui/account/Account.gd @@ -0,0 +1,75 @@ +extends Control + +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 +var signed_in = false + + +func _ready(): + Globals.network.connect("signinresult", self, "_on_signin_result") + Globals.network.connect("signupresult", self, "_on_signup_result") + Globals.network.connect("connection_established", self, "attempt_autologin") + flags.append_array(Utils.walk_dir("res://assets/flags", false, "png", ["rainbow"])) + for i in flags: # add the items + flagchoice.add_icon_item(load("res://assets/flags/" + i + ".png"), i.replace("_", " ")) + + +func attempt_autologin(): + autologin = true + if data.name and data.password: + Globals.network.signin(data) + Log.info("Attempting autologin") + autologin = false + + +func _on_signin_result(result): + $choose/signin/signinbutton.disabled = false + if typeof(result) == TYPE_STRING: # ew, error, get it away from me + Log.err(result) + return + data.id = result.id + data.country = result.country + save_data() + status.set_text("Sign in sucessfull!") + signed_in = true # yay + + +func _on_signup_result(result: String): + $choose/signup/signupbutton.disabled = false + if "err:" in result: # ew error go awway + Log.err(result) + return + data.id = result + save_data() + status.set_text("Sign up sucessfull ( you are now logged in )!") + signed_in = true # yay + + +func _on_signup_pressed(): + $choose/signup/signupbutton.disabled = true + update_data(signup.username, signup.pw) + Globals.network.signup(data) + + +func update_data(username, pw): + username.text = username.get_text().strip_edges().strip_escapes() + data.name = username.get_text() + data.password = pw.get_text() + save_data() + + +func save_data(): + SaveLoad.files.id.data = data + SaveLoad.save("id") + + +func _on_signin_pressed(): + $choose/signin/signinbutton.disabled = true + update_data(signin.username, signin.pw) + Globals.network.signin(data) diff --git a/ui/account/Account.tscn b/ui/account/Account.tscn new file mode 100644 index 0000000..c71e953 --- /dev/null +++ b/ui/account/Account.tscn @@ -0,0 +1,114 @@ +[gd_scene load_steps=11 format=2] + +[ext_resource path="res://assets/ui/verdana-bold.ttf" type="DynamicFontData" id=1] +[ext_resource path="res://assets/ui/Roboto-Medium.ttf" type="DynamicFontData" id=2] +[ext_resource path="res://ui/theme/main.tres" type="Theme" id=3] +[ext_resource path="res://ui/verdana.tres" type="DynamicFont" id=4] +[ext_resource path="res://ui/account/usernamepass.tscn" type="PackedScene" id=5] +[ext_resource path="res://ui/account/Account.gd" type="Script" id=6] +[ext_resource path="res://ui/Status.gd" type="Script" id=7] + +[sub_resource type="DynamicFont" id=1] +size = 30 +font_data = ExtResource( 2 ) + +[sub_resource type="DynamicFont" id=3] +size = 15 +font_data = ExtResource( 1 ) + +[sub_resource type="Theme" id=2] +default_font = SubResource( 3 ) +OptionButton/fonts/font = ExtResource( 4 ) + +[node name="account" type="VBoxContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +theme = ExtResource( 3 ) +script = ExtResource( 6 ) + +[node name="Disclaimer" type="Label" parent="."] +margin_right = 1422.0 +margin_bottom = 75.0 +rect_min_size = Vector2( 700, 0 ) +custom_fonts/font = SubResource( 1 ) +text = "Create account, OR play anonymous. When playing anonymously, upon exiting the game, you will be unable to rejoin." +autowrap = true + +[node name="StatusLabel" type="Label" parent="."] +margin_top = 90.0 +margin_right = 1422.0 +margin_bottom = 140.0 +script = ExtResource( 7 ) + +[node name="choose" type="TabContainer" parent="."] +margin_top = 155.0 +margin_right = 1422.0 +margin_bottom = 800.0 +size_flags_vertical = 3 + +[node name="signup" type="VBoxContainer" parent="choose"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 30.0 +margin_top = 90.0 +margin_right = -30.0 +margin_bottom = -30.0 +custom_constants/separation = 5 + +[node name="usernamepass" parent="choose/signup" instance=ExtResource( 5 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 177.0 +margin_right = 1185.0 +margin_bottom = 227.0 + +[node name="flag" type="OptionButton" parent="choose/signup"] +visible = false +margin_left = 531.0 +margin_top = 230.0 +margin_right = 831.0 +margin_bottom = 336.0 +rect_min_size = Vector2( 300, 0 ) +focus_mode = 0 +size_flags_horizontal = 4 +theme = SubResource( 2 ) +text = "flag" +align = 1 +expand_icon = true + +[node name="signupbutton" type="Button" parent="choose/signup"] +margin_left = 566.0 +margin_top = 232.0 +margin_right = 796.0 +margin_bottom = 338.0 +rect_min_size = Vector2( 230, 0 ) +focus_mode = 0 +size_flags_horizontal = 4 +enabled_focus_mode = 0 +text = "sign up" + +[node name="signin" type="VBoxContainer" parent="choose"] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 30.0 +margin_top = 90.0 +margin_right = -30.0 +margin_bottom = -30.0 +custom_constants/separation = 5 + +[node name="usernamepass" parent="choose/signin" instance=ExtResource( 5 )] + +[node name="signinbutton" type="Button" parent="choose/signin"] +margin_left = 577.0 +margin_top = 240.0 +margin_right = 785.0 +margin_bottom = 346.0 +rect_min_size = Vector2( 230, 0 ) +focus_mode = 0 +size_flags_horizontal = 4 +enabled_focus_mode = 0 +text = "sign in" + +[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/Password.gd b/ui/account/Password.gd new file mode 100644 index 0000000..32db589 --- /dev/null +++ b/ui/account/Password.gd @@ -0,0 +1 @@ +extends Restrict diff --git a/ui/account/Restrict.gd b/ui/account/Restrict.gd new file mode 100644 index 0000000..5786d80 --- /dev/null +++ b/ui/account/Restrict.gd @@ -0,0 +1,8 @@ +extends LineEdit +class_name Restrict + + +func _on_text_changed(new_text: String): + var pos = caret_position + text = new_text.strip_edges() + caret_position = pos diff --git a/ui/account/Secret.gd b/ui/account/Secret.gd new file mode 100644 index 0000000..1410025 --- /dev/null +++ b/ui/account/Secret.gd @@ -0,0 +1,20 @@ +extends TextureButton + +export(Color) var focusedcolor := Color(0.396078, 0.423529, 0.45098) +export(Texture) var map_tex + +onready var pw := $"../Password" + + +func _ready() -> void: + var bitmap := BitMap.new() + bitmap.create_from_image_alpha(map_tex.get_data()) + texture_click_mask = bitmap + + +func _focus(what: bool) -> void: + modulate = focusedcolor if what else Color.white + + +func _toggled(button_pressed: bool) -> void: + pw.secret = button_pressed diff --git a/ui/account/Username.gd b/ui/account/Username.gd new file mode 100644 index 0000000..32db589 --- /dev/null +++ b/ui/account/Username.gd @@ -0,0 +1 @@ +extends Restrict diff --git a/ui/account/usernamepass.gd b/ui/account/usernamepass.gd new file mode 100644 index 0000000..387b806 --- /dev/null +++ b/ui/account/usernamepass.gd @@ -0,0 +1,5 @@ +extends Control +class_name UsernamePass + +onready var username = $Username +onready var pw = $H/Password diff --git a/ui/account/usernamepass.tscn b/ui/account/usernamepass.tscn new file mode 100644 index 0000000..e28da26 --- /dev/null +++ b/ui/account/usernamepass.tscn @@ -0,0 +1,75 @@ +[gd_scene load_steps=9 format=2] + +[ext_resource path="res://assets/ui/eyemap.png" type="Texture" id=1] +[ext_resource path="res://ui/account/Secret.gd" type="Script" id=2] +[ext_resource path="res://assets/ui/eye.png" type="Texture" id=3] +[ext_resource path="res://assets/ui/eye_off.png" type="Texture" id=4] +[ext_resource path="res://ui/theme/main.tres" type="Theme" id=5] +[ext_resource path="res://ui/account/usernamepass.gd" type="Script" id=6] +[ext_resource path="res://ui/account/Password.gd" type="Script" id=7] +[ext_resource path="res://ui/account/Username.gd" type="Script" id=8] + +[node name="usernamepass" type="VBoxContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_min_size = Vector2( 0, 225 ) +size_flags_horizontal = 4 +size_flags_vertical = 4 +theme = ExtResource( 5 ) +alignment = 1 +script = ExtResource( 6 ) + +[node name="Username" type="LineEdit" parent="."] +margin_left = 361.0 +margin_top = 286.0 +margin_right = 1061.0 +margin_bottom = 392.0 +rect_min_size = Vector2( 700, 0 ) +size_flags_horizontal = 4 +size_flags_vertical = 0 +max_length = 20 +placeholder_text = "username goes here" +caret_blink = true +caret_blink_speed = 0.5 +script = ExtResource( 8 ) + +[node name="H" type="HBoxContainer" parent="."] +margin_top = 407.0 +margin_right = 1422.0 +margin_bottom = 513.0 +custom_constants/separation = 2 +alignment = 1 + +[node name="Password" type="LineEdit" parent="H"] +margin_left = 207.0 +margin_right = 1107.0 +margin_bottom = 106.0 +rect_min_size = Vector2( 900, 0 ) +size_flags_horizontal = 4 +size_flags_vertical = 4 +max_length = 20 +context_menu_enabled = false +placeholder_text = "password goes here" +caret_blink = true +caret_blink_speed = 0.5 +script = ExtResource( 7 ) + +[node name="Secret" type="TextureButton" parent="H"] +margin_left = 1109.0 +margin_right = 1215.0 +margin_bottom = 106.0 +rect_min_size = Vector2( 106, 106 ) +focus_mode = 1 +toggle_mode = true +enabled_focus_mode = 1 +texture_normal = ExtResource( 3 ) +texture_pressed = ExtResource( 4 ) +expand = true +script = ExtResource( 2 ) +focusedcolor = Color( 0.921569, 0.827451, 0.827451, 1 ) +map_tex = ExtResource( 1 ) + +[connection signal="text_changed" from="Username" to="Username" method="_on_text_changed"] +[connection signal="text_changed" from="H/Password" to="H/Password" method="_on_text_changed"] +[connection signal="mouse_entered" from="H/Secret" to="H/Secret" method="_focus" binds= [ true ]] +[connection signal="mouse_exited" from="H/Secret" to="H/Secret" method="_focus" binds= [ false ]] diff --git a/ui/barbutton/drawbutton.gd b/ui/barbutton/drawbutton.gd index 13f4965..6aa3cb4 100644 --- a/ui/barbutton/drawbutton.gd +++ b/ui/barbutton/drawbutton.gd @@ -8,7 +8,8 @@ var waiting_on_answer := false func _ready(): - Globals.network.connect("signal_recieved", self, "_on_signal") + if Globals.network: + Globals.network.connect("signal_recieved", self, "_on_signal") func _on_signal(what: Dictionary): diff --git a/ui/barbutton/resignbutton.gd b/ui/barbutton/resignbutton.gd index 2a03c3e..e66c42a 100644 --- a/ui/barbutton/resignbutton.gd +++ b/ui/barbutton/resignbutton.gd @@ -9,7 +9,8 @@ var waiting_on_answer = false func _ready() -> void: - Globals.network.connect("signal_recieved", self, "resigned") + if Globals.network: + Globals.network.connect("signal_recieved", self, "resigned") func resigned(what: Dictionary): diff --git a/ui/colorpicker/ColorPickerButton.tscn b/ui/colorpicker/ColorPickerButton.tscn index df3ed57..36c89d8 100644 --- a/ui/colorpicker/ColorPickerButton.tscn +++ b/ui/colorpicker/ColorPickerButton.tscn @@ -21,7 +21,6 @@ text = "button" script = ExtResource( 2 ) [node name="Popup" type="PopupPanel" parent="."] -visible = true margin_left = 50.0 margin_top = 50.0 margin_right = 195.0 diff --git a/ui/theme/flatblack.tres b/ui/theme/flatblack.tres index 81baf6d..9aefd64 100644 --- a/ui/theme/flatblack.tres +++ b/ui/theme/flatblack.tres @@ -1,5 +1,7 @@ [gd_resource type="StyleBoxFlat" format=2] [resource] -bg_color = Color( 0, 0, 0, 1 ) +content_margin_left = 10.0 +content_margin_right = 10.0 +bg_color = Color( 0, 0, 0, 0.882353 ) corner_detail = 20 diff --git a/ui/theme/main.tres b/ui/theme/main.tres index 333e5d3..c729f76 100644 --- a/ui/theme/main.tres +++ b/ui/theme/main.tres @@ -33,6 +33,8 @@ border_width_right = 2 border_width_bottom = 2 border_color = Color( 1, 1, 1, 1 ) corner_detail = 20 +expand_margin_left = 10.0 +expand_margin_right = 10.0 [sub_resource type="StyleBoxFlat" id=10] content_margin_left = 30.0 |