diff --git a/assets/source/Bitstream Vera Sans Mono Bold Nerd Font Complete.ttf b/assets/source/Bitstream Vera Sans Mono Bold Nerd Font Complete.ttf new file mode 100644 index 0000000..e63b764 Binary files /dev/null and b/assets/source/Bitstream Vera Sans Mono Bold Nerd Font Complete.ttf differ diff --git a/src/Game.gd b/src/Game.gd index d705fc1..eaf57ed 100644 --- a/src/Game.gd +++ b/src/Game.gd @@ -17,6 +17,22 @@ var width var landing_piece = null var landing_pos = null +var current_state = 0 +var states = { + 0: { + "description": "Player turn", + "directive": "Make a move", + }, + 1: { + "description": "Opponent's turn", + "directive": "Wait for the opponent to make a move", + } +} +var flash_help = null +var rng = null +var ai_target = null +var ai_piece = null + const piece_types = { "pawn": "res://src/pieces/Pawn.tscn", "rook": "res://src/pieces/Rook.tscn", @@ -30,8 +46,9 @@ func new_piece(piece_type, group, position = null): if not piece_types.has(piece_type): return null var piece = ResourceLoader.load(piece_types[piece_type]).instance() - if position: + if position != null: if !set_piece_position(piece, position, true): + print("Failed to place piece ", piece, " at positiion ", position) piece.queue_free() return null piece.set_position(Vector2( @@ -47,46 +64,82 @@ func new_piece(piece_type, group, position = null): ) pf.add_child(piece) piece.connect("click", self, "_on_piece_click") - piece.connect("hold_start", self, "_on_piece_click") + piece.connect("hold_start", self, "_on_hold_start") piece.connect("hold_stop", self, "_on_hold_stop") #print("Created piece ", piece, " of type ", piece_type, " at ", position, " for group ", group) return piece +func _on_hold_start(piece, event): + if piece.is_in_group("player"): + if self.current_state != 0: + print("Can't move a piece, it's not your turn") + get_node("BottomBar/Help").set_text("Cannot move a piece, it's not your turn") + self.flash_help = 2 + piece.cancel_hold() + return + self._on_piece_click(piece, event) + else: + print("Can't move opponent's piece: ", piece) + get_node("BottomBar/Help").set_text("Cannot move an opponent's piece") + self.flash_help = 3 + piece.cancel_hold() + func _on_hold_stop(piece, event): - _on_piece_click(piece, null) + if self.selected_piece != null: + # deselect + var p = self.selected_piece + if p.is_in_group("player"): + p.get_node("Body").set_modulate(Color(1, 1, 1, 1)) + else: + p.get_node("Body").set_modulate(Color(0.8, .01, .01, 1)) + var moves = get_valid_piece_moves(p) + clear_square_hilights_for_moves(moves) + self.selected_piece = null # Try to land the piece on the next physics frame so we # can use raycasts self.landing_piece = piece self.landing_pos = event.position -func get_valid_piece_moves(piece): +func get_valid_piece_moves(piece, verbose = false): var square = square_of_piece(piece) var possible_moves = piece.get_possible_moves(Vector2(square.x, square.y)) # @TODO Filter based on game state var moves = [] - for m in possible_moves: - var target_square = null - if self.board_squares.has(m['pos']): - target_square = self.board_squares[m['pos']] - if target_square == null: - # Move it off the board - print("Move to ", m['pos'], " is not valid due to not being on the board") - continue - if target_square['piece']: - # something here - if pieces_hostile(piece, target_square['piece']): - if not m['attack']: - print("Move to ", m['pos'], " is not valid due to not being an attack move") + for d in possible_moves: + for m in d: + var target_square = null + if self.board_squares.has(m['pos']): + target_square = self.board_squares[m['pos']] + if target_square == null: + # Move it off the board + if verbose: + print("Move to ", m['pos'], " is not valid due to not being on the board") + break + if target_square['piece']: + # something here + if pieces_hostile(piece, target_square['piece']): + if not m['attack']: + if verbose: + print("Move to ", m['pos'], " is not valid due to not being an attack move") + break + else: + m["target"] = target_square['piece'] + moves.append(m) + if not m['jump']: + break else: + if verbose: + print("Move to ", m['pos'], " is not valid due to same-team piece is spot") + if not m['jump']: + break + else: + # empty + if not m['attack_only']: moves.append(m) - else: - print("Move to ", m['pos'], " is not valid due to same-team piece is spot") - else: - # empty - if not m['attack_only']: - moves.append(m) - else: - print("Move to ", m['pos'], " is not valid due to missing target") + else: + if verbose: + print("Move to ", m['pos'], " is not valid due to missing target") + break return moves func pieces_hostile(p1, p2): @@ -183,6 +236,8 @@ func _ready(): 64 * scale )) self.pf_scale = scale + self.rng = RandomNumberGenerator.new() + self.rng.randomize() reset_game_state() func reset_game_state(): @@ -216,8 +271,8 @@ func reset_game_state(): new_piece("pawn", "player", Vector2(x, y_player)) new_piece("pawn", "opponent", Vector2(x, y_opponent)) i += 1 - new_piece("rook", "player", Vector2(start_x, self.height -1)) - new_piece("rook", "player", Vector2(start_x + 7, self.height -1)) + new_piece("rook", "player", Vector2(start_x, self.height - 1)) + new_piece("rook", "player", Vector2(start_x + 7, self.height - 1)) new_piece("rook", "opponent", Vector2(start_x, 0)) new_piece("rook", "opponent", Vector2(start_x + 7, 0)) new_piece("bishop", "player", Vector2(start_x + 2, self.height -1)) @@ -231,11 +286,85 @@ func reset_game_state(): new_piece("king", "player", Vector2(start_x + 4, self.height - 1)) new_piece("king", "opponent", Vector2(start_x + 4, 0)) new_piece("queen", "player", Vector2(start_x + 3, self.height - 1)) - new_piece("queen", "opponent", Vector2(start_x + 3, 0)) + new_piece("queen", "opponent", Vector2(start_x + 3, 0)) + + # Visual updates + self.current_state = 3 + self.turn = 0 + self._on_phase_end() + self._reset_help() + +func _on_phase_end(): + self.current_state += 1; + if not self.current_state in self.states.keys(): + self.current_state = 0 + self.turn += 1 + self._on_new_turn() + get_node("TopBar/Bottom/Instruction").set_text( + self.states[self.current_state]["description"] + " - " + self.states[self.current_state]["directive"] + ) + +func _on_new_turn(): + get_node("TopBar/Top/HBoxContainer/Turn").set_text(str(self.turn)) + +func _reset_help(): + get_node("BottomBar/Help").set_text("Delay losing as long as possible. Your loss is inevitable.\nClick on piece to see it's possible moves and stats.\nDrag and drop a piece to make a move.") # Called every frame. 'delta' is the elapsed time since the previous frame. -#func _process(delta): -# pass +func _process(delta): + if self.flash_help != null: + if self.flash_help > 0: + self.flash_help -= delta + else: + self._reset_help() + self.flash_help = null + if self.current_state == 1: + if self.ai_target != null: + var target_square = self.board_squares[self.ai_target] + var target = get_node("/root/Game/MarginContainer/Playfield").squares[self.ai_target].get_ref() + if self.ai_piece.get_global_position().distance_to(target.get_global_position()) >= 5.0: + self.ai_piece.set_global_position( + self.ai_piece.get_global_position().move_toward( + target.get_global_position(), + 5.0 + ) + ) + else: + # End movement + if target_square['piece'] != null: + # @TODO If the target doesn't die, we need to bounce back + target_square['piece'].queue_free() + var square = square_of_piece(self.ai_piece) + square['piece'] = null + target_square['piece'] = self.ai_piece + self.ai_piece.set_position(Vector2(target_square['x']*128, target_square['y']*128)) + self.ai_piece.at_spawn = false + self.ai_target = null + self.ai_piece = null + self._on_phase_end() + else: + # @TODO Find a way to run in a BG thread or split workload across frames + # if it takes too long to narrow down a move. + var moves = [] + for piece in get_tree().get_nodes_in_group("opponent"): + moves += get_valid_piece_moves(piece) + # Our highest priority moves are to take another piece + var priority_moves = [] + for m in moves: + if m.has("target"): + priority_moves.append(m) + if not priority_moves.empty(): + # @TODO Check for the most "valuable" piece to take + var i = self.rng.randi() % (priority_moves.size()) + self.ai_target = priority_moves[i]['pos'] + self.ai_piece = priority_moves[i]['source'] + else: + # @TODO Sort our moves to try and get the furthest forward + # possible + var i = self.rng.randi() % (moves.size()) + self.ai_target = moves[i]['pos'] + self.ai_piece = moves[i]['source'] + print("Opponent moving ", self.ai_piece, " to ", self.ai_target, " from ", square_of_piece(self.ai_piece)) func _physics_process(delta): if self.landing_piece != null: @@ -276,6 +405,7 @@ func _physics_process(delta): dest_square['piece'] = piece piece.set_position(Vector2(dest_square['x']*128, dest_square['y']*128)) piece.at_spawn = false + self._on_phase_end() else: # invalid destination bounce back piece.set_position(Vector2(square['x']*128, square['y']*128)) diff --git a/src/Game.tscn b/src/Game.tscn index b2c4388..6aec4f2 100644 --- a/src/Game.tscn +++ b/src/Game.tscn @@ -1,7 +1,13 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=6 format=2] [ext_resource path="res://src/Game.gd" type="Script" id=1] [ext_resource path="res://src/Playfield.tscn" type="PackedScene" id=2] +[ext_resource path="res://src/large font.tres" type="DynamicFont" id=3] +[ext_resource path="res://assets/source/Bitstream Vera Sans Mono Bold Nerd Font Complete.ttf" type="DynamicFontData" id=4] + +[sub_resource type="DynamicFont" id=1] +size = 24 +font_data = ExtResource( 4 ) [node name="Game" type="Container"] anchor_right = 1.0 @@ -25,6 +31,23 @@ size_flags_vertical = 2 [node name="Playfield" parent="MarginContainer" instance=ExtResource( 2 )] +[node name="BottomBar" type="CenterContainer" parent="."] +anchor_bottom = 1.0 +margin_left = 256.0 +margin_top = 896.0 +margin_right = 1024.0 +rect_min_size = Vector2( 0, 64 ) + +[node name="Help" type="Label" parent="BottomBar"] +margin_left = 146.0 +margin_top = 31.0 +margin_right = 622.0 +margin_bottom = 95.0 +rect_min_size = Vector2( 0, 64 ) +custom_fonts/font = SubResource( 1 ) +text = "This is why you are doing it wrong" +valign = 1 + [node name="TopBar" type="VBoxContainer" parent="."] margin_left = 256.0 margin_right = 1024.0 @@ -36,20 +59,22 @@ margin_bottom = 64.0 rect_min_size = Vector2( 0, 64 ) [node name="HBoxContainer" type="HBoxContainer" parent="TopBar/Top"] -margin_left = 364.0 -margin_top = 25.0 -margin_right = 404.0 -margin_bottom = 39.0 +margin_left = 334.0 +margin_top = 13.0 +margin_right = 433.0 +margin_bottom = 51.0 [node name="TurnLabel" type="Label" parent="TopBar/Top/HBoxContainer"] -margin_right = 28.0 -margin_bottom = 14.0 +margin_right = 76.0 +margin_bottom = 38.0 +custom_fonts/font = ExtResource( 3 ) text = "Turn" [node name="Turn" type="Label" parent="TopBar/Top/HBoxContainer"] -margin_left = 32.0 -margin_right = 40.0 -margin_bottom = 14.0 +margin_left = 80.0 +margin_right = 99.0 +margin_bottom = 38.0 +custom_fonts/font = ExtResource( 3 ) text = "0" __meta__ = { "_edit_use_anchors_": false @@ -62,10 +87,10 @@ margin_bottom = 132.0 rect_min_size = Vector2( 0, 64 ) [node name="Instruction" type="Label" parent="TopBar/Bottom"] -margin_left = 344.0 -margin_right = 424.0 +margin_left = 314.0 +margin_right = 454.0 margin_bottom = 64.0 rect_min_size = Vector2( 0, 64 ) +custom_fonts/font = SubResource( 1 ) text = "Do a thing" valign = 1 -uppercase = true diff --git a/src/Piece.gd b/src/Piece.gd index 5085479..a869e3b 100644 --- a/src/Piece.gd +++ b/src/Piece.gd @@ -13,7 +13,7 @@ signal click(piece, event) # var health = 1 var damage = 1 -var speed = 8 +var speed = 7 var jump = false var abilities = [] var kills = 0 @@ -53,8 +53,7 @@ func _input(event): if not event.pressed and self.hold_started: emit_signal("hold_stop", self, event) print("Hold stop", self, " ", event) - self.hold_started = false - self.last_click = null + self.cancel_hold() get_tree().set_input_as_handled() # Called every frame. 'delta' is the elapsed time since the previous frame. @@ -68,28 +67,16 @@ func _process(delta): if self.hold_started: self.set_global_position(get_viewport().get_mouse_position()) - +func cancel_hold(): + self.last_click = null + self.hold_started = false -#func _on_Piece_input_event(viewport, event, shape_idx): -# print("piece input event: ", event) -# if event is InputEventMouseButton and event.button_index == 1: -# if event.pressed: -# if self.last_click == null: -# set_process_input(true) -# self.last_click = 0 -# else: -# if self.last_click <= CLICK_THRESHOLD: -# emit_signal("click", self, event) -# else: -# emit_signal("hold_stop", self, event) -# self.hold_started = false -# self.last_click = null - -#static func get_move_dict() { -# var x = { -# "attack": false, -# "pos": null, -# "attack_only": false, -# } -# return x -#} +func init_move_dict(position): + var x = { + "attack": true, + "pos": position, + "attack_only": false, + "jump": false, + "source": self, + } + return x diff --git a/src/large font.tres b/src/large font.tres new file mode 100644 index 0000000..a5f1fbe --- /dev/null +++ b/src/large font.tres @@ -0,0 +1,7 @@ +[gd_resource type="DynamicFont" load_steps=2 format=2] + +[ext_resource path="res://assets/source/Bitstream Vera Sans Mono Bold Nerd Font Complete.ttf" type="DynamicFontData" id=1] + +[resource] +size = 32 +font_data = ExtResource( 1 ) diff --git a/src/pieces/Bishop.gd b/src/pieces/Bishop.gd new file mode 100644 index 0000000..b5f80e8 --- /dev/null +++ b/src/pieces/Bishop.gd @@ -0,0 +1,34 @@ +extends Piece + + +# Declare member variables here. Examples: +# var a = 2 +# var b = "text" + + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + +func get_possible_moves(position): + var directions = [ + Vector2(1, 1), + Vector2(1, -1), + Vector2(-1, -1), + Vector2(-1, 1), + ] + var options = [] + for d in directions: + var d_opts = [] + var i = 1 + while i < self.speed + 1: + var opt = self.init_move_dict( + Vector2(position.x+(d.x*i), position.y+(d.y)*i) + ) + d_opts.append(opt) + i += 1 + options.append(d_opts) + return options +# Called every frame. 'delta' is the elapsed time since the previous frame. +#func _process(delta): +# pass diff --git a/src/pieces/Bishop.tscn b/src/pieces/Bishop.tscn index 68c7739..9338880 100644 --- a/src/pieces/Bishop.tscn +++ b/src/pieces/Bishop.tscn @@ -1,9 +1,11 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=4 format=2] [ext_resource path="res://src/pieces/Piece.tscn" type="PackedScene" id=1] [ext_resource path="res://assets/export/bishop.png" type="Texture" id=2] +[ext_resource path="res://src/pieces/Bishop.gd" type="Script" id=3] [node name="Piece" instance=ExtResource( 1 )] +script = ExtResource( 3 ) [node name="Body" parent="." index="0"] texture = ExtResource( 2 ) diff --git a/src/pieces/King.gd b/src/pieces/King.gd new file mode 100644 index 0000000..f91838b --- /dev/null +++ b/src/pieces/King.gd @@ -0,0 +1,39 @@ +extends Piece + + +# Declare member variables here. Examples: +# var a = 2 +# var b = "text" + + +# Called when the node enters the scene tree for the first time. +func _ready(): + self.speed = 1 + pass # Replace with function body. + +func get_possible_moves(position): + var directions = [ + Vector2(1, 0), + Vector2(1, 1), + Vector2(0, 1), + Vector2(-1, 1), + Vector2(-1, 0), + Vector2(-1, -1), + Vector2(0, -1), + Vector2(1, -1), + ] + var options = [] + for d in directions: + var d_opts = [] + var i = 1 + while i < self.speed + 1: + var opt = self.init_move_dict( + Vector2(position.x+(d.x*i), position.y+(d.y)*i) + ) + d_opts.append(opt) + i += 1 + options.append(d_opts) + return options +# Called every frame. 'delta' is the elapsed time since the previous frame. +#func _process(delta): +# pass diff --git a/src/pieces/King.tscn b/src/pieces/King.tscn index ed4dda7..d58053c 100644 --- a/src/pieces/King.tscn +++ b/src/pieces/King.tscn @@ -1,9 +1,11 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=4 format=2] [ext_resource path="res://src/pieces/Piece.tscn" type="PackedScene" id=1] [ext_resource path="res://assets/export/king.png" type="Texture" id=2] +[ext_resource path="res://src/pieces/King.gd" type="Script" id=3] [node name="Piece" instance=ExtResource( 1 )] +script = ExtResource( 3 ) [node name="Body" parent="." index="0"] texture = ExtResource( 2 ) diff --git a/src/pieces/Knight.gd b/src/pieces/Knight.gd new file mode 100644 index 0000000..61dcb8b --- /dev/null +++ b/src/pieces/Knight.gd @@ -0,0 +1,39 @@ +extends Piece + + +# Declare member variables here. Examples: +# var a = 2 +# var b = "text" + + +# Called when the node enters the scene tree for the first time. +func _ready(): + self.speed = 1 + +func get_possible_moves(position): + var directions = [ + Vector2(2, 1), + Vector2(2, -1), + Vector2(1, 2), + Vector2(-1, 2), + Vector2(-2, 1), + Vector2(-2, -1), + Vector2(-1, -2), + Vector2(1, -2), + ] + var options = [] + for d in directions: + var d_opts = [] + var i = 1 + while i < self.speed + 1: + var opt = self.init_move_dict( + Vector2(position.x+(d.x*i), position.y+(d.y)*i) + ) + opt.jump = true + d_opts.append(opt) + i += 1 + options.append(d_opts) + return options +# Called every frame. 'delta' is the elapsed time since the previous frame. +#func _process(delta): +# pass diff --git a/src/pieces/Knight.tscn b/src/pieces/Knight.tscn index d60f9f2..9671294 100644 --- a/src/pieces/Knight.tscn +++ b/src/pieces/Knight.tscn @@ -1,9 +1,11 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=4 format=2] [ext_resource path="res://src/pieces/Piece.tscn" type="PackedScene" id=1] [ext_resource path="res://assets/export/knight.png" type="Texture" id=2] +[ext_resource path="res://src/pieces/Knight.gd" type="Script" id=3] [node name="Piece" instance=ExtResource( 1 )] +script = ExtResource( 3 ) [node name="Body" parent="." index="0"] texture = ExtResource( 2 ) diff --git a/src/pieces/Pawn.gd b/src/pieces/Pawn.gd index 85c4b74..b18e708 100644 --- a/src/pieces/Pawn.gd +++ b/src/pieces/Pawn.gd @@ -22,31 +22,28 @@ func get_possible_moves(position): # implemented by our children is_player = true var options = [] - print(self, " at current position: ", position) - print("Is player piece: ", is_player, " and is going in direction: ", forward) + #print(self, " at current position: ", position) + #print("Is player piece: ", is_player, " and is going in direction: ", forward) var i = 1; while i < self.speed+1: - options.append({ - "pos": Vector2(position.x, position.y + i*forward.y), - "attack": false, - "attack_only": false, - }) + var o = self.init_move_dict(Vector2(position.x, position.y + i*forward.y)) + o.attack = false + options.append(o) i += 1 + options = [options] if self.at_spawn: - options.append({ - "pos": Vector2(position.x, position.y + (self.speed+1)*forward.y), - "attack": false, - "attack_only": false, - }) + var o = self.init_move_dict(Vector2(position.x, position.y + (self.speed+1)*forward.y)) + o.attack = false + options[0].append(o) for d in attacks: + var d_opts = [] i = 1 while i < self.speed+1: - options.append({ - "pos": Vector2(position.x + i*d.x, position.y + i*d.y), - "attack": true, - "attack_only": true, - }) + var o = self.init_move_dict(Vector2(position.x + i*d.x, position.y + i*d.y)) + o.attack_only = true + d_opts.append(o) i += 1 + options.append(d_opts) return options # Called every frame. 'delta' is the elapsed time since the previous frame. diff --git a/src/pieces/Queen.gd b/src/pieces/Queen.gd new file mode 100644 index 0000000..1f5add1 --- /dev/null +++ b/src/pieces/Queen.gd @@ -0,0 +1,38 @@ +extends Piece + + +# Declare member variables here. Examples: +# var a = 2 +# var b = "text" + + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + +func get_possible_moves(position): + var directions = [ + Vector2(1, 0), + Vector2(1, 1), + Vector2(0, 1), + Vector2(-1, 1), + Vector2(-1, 0), + Vector2(-1, -1), + Vector2(0, -1), + Vector2(1, -1), + ] + var options = [] + for d in directions: + var d_opts = [] + var i = 1 + while i < self.speed + 1: + var opt = self.init_move_dict( + Vector2(position.x+(d.x*i), position.y+(d.y)*i) + ) + d_opts.append(opt) + i += 1 + options.append(d_opts) + return options +# Called every frame. 'delta' is the elapsed time since the previous frame. +#func _process(delta): +# pass diff --git a/src/pieces/Queen.tscn b/src/pieces/Queen.tscn index 0a25912..2cbdb7e 100644 --- a/src/pieces/Queen.tscn +++ b/src/pieces/Queen.tscn @@ -1,9 +1,11 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=4 format=2] [ext_resource path="res://src/pieces/Piece.tscn" type="PackedScene" id=1] [ext_resource path="res://assets/export/queen.png" type="Texture" id=2] +[ext_resource path="res://src/pieces/Queen.gd" type="Script" id=3] [node name="Piece" instance=ExtResource( 1 )] +script = ExtResource( 3 ) [node name="Body" parent="." index="0"] texture = ExtResource( 2 ) diff --git a/src/pieces/Rook.gd b/src/pieces/Rook.gd new file mode 100644 index 0000000..8ab36a3 --- /dev/null +++ b/src/pieces/Rook.gd @@ -0,0 +1,34 @@ +extends Piece + + +# Declare member variables here. Examples: +# var a = 2 +# var b = "text" + + +# Called when the node enters the scene tree for the first time. +func _ready(): + pass # Replace with function body. + +func get_possible_moves(position): + var directions = [ + Vector2(1, 0), + Vector2(-1, 0), + Vector2(0, -1), + Vector2(0, 1), + ] + var options = [] + for d in directions: + var d_opts = [] + var i = 1 + while i < self.speed + 1: + var opt = self.init_move_dict( + Vector2(position.x+(d.x*i), position.y+(d.y)*i) + ) + d_opts.append(opt) + i += 1 + options.append(d_opts) + return options +# Called every frame. 'delta' is the elapsed time since the previous frame. +#func _process(delta): +# pass diff --git a/src/pieces/Rook.tscn b/src/pieces/Rook.tscn index 106525c..44fd8f2 100644 --- a/src/pieces/Rook.tscn +++ b/src/pieces/Rook.tscn @@ -1,9 +1,11 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=4 format=2] [ext_resource path="res://src/pieces/Piece.tscn" type="PackedScene" id=1] [ext_resource path="res://assets/export/rook.png" type="Texture" id=2] +[ext_resource path="res://src/pieces/Rook.gd" type="Script" id=3] [node name="Rook" instance=ExtResource( 1 )] +script = ExtResource( 3 ) [node name="Body" parent="." index="0"] texture = ExtResource( 2 )