extends Node class_name game var player_gold: int = 10 var shop_scene = preload("res://scenes/Shop.tscn") var shop: Node2D var purchased_pieces: Array = [] var purchased_upgrades: Array = [] @export var tile_size = 64 @export var dark = Color(0,0,0,1) @export var light =Color(1,1,1,1) @onready var dark_tile_shader: ShaderMaterial = ShaderMaterial.new() @onready var light_tile_shader: ShaderMaterial = ShaderMaterial.new() @onready var opponent_shader: ShaderMaterial = ShaderMaterial.new() @onready var background = $Background @onready var tile_container = $TileContainer @onready var shadow_container = $ShadowContainer @onready var piece_container = $PieceContainer @onready var board = [] @onready var player_pieces = $PlayerPieces @onready var player_piece_count = 0 @onready var opponent_pieces = $OpponentPieces @onready var opponent_piece_count = 0 @onready var gold_display: Label = $GoldDisplay var selected_piece var selected_piece_value var selected_piece_position var valid_moves = [] @onready var selected_shader: ShaderMaterial = ShaderMaterial.new() var board_width = 8 var board_height = 8 var target_position var moving: bool var sprite: Sprite2D var player_turn : bool = true @onready var explosion_effect = $Explosion func _ready() -> void: shop = shop_scene.instantiate() add_child(shop) var viewport_size = get_viewport().get_visible_rect().size shop.position = (viewport_size / 2) - Vector2(570, 650) / 2 # pretty much centered, not sure on the exact values / how to scale it to screen size better shop.visible = false # Start hidden update_gold_display() # Initialize gold display set_gold_display_position() # Set the anchors and margins for the gold display shop.purchase_attempted.connect(_on_purchase_attempted) shop.next_round_requested.connect(_on_next_round_requested) # Connect the resize signal get_viewport().connect("size_changed", Callable(self, "_on_viewport_size_changed")) DisplayServer.window_set_min_size(Vector2(1152, 648)) # Set minimum window size #creates 2D array for board, with empty (null) spaces for x in range(board_width): board.append([]) for y in range(board_height): board[x].append(null) #hard-coded setup of pieces board[0][7] = Rook.new(true, Vector2(0, 7)) board[1][6] = Wizard.new(true, Vector2(0, 7)) board[1][7] = Bishop.new(true, Vector2(1, 7)) board[2][7] = King.new(true, Vector2(2, 7)) board[3][6] = Queen.new(true, Vector2(3, 6)) board[4][2] = Pawn.new(true, Vector2(4, 7)) board[5][7] = Knight.new(true, Vector2(5, 7)) board[6][7] = Assassin.new(true, Vector2(6, 7)) board[7][6] = Mage.new(true, Vector2(7, 6)) board[7][7] = Rook.new(true, Vector2(7, 7)) board[0][4] = Rook.new(false, Vector2(0, 4)) board[1][5] = Bishop.new(false, Vector2(1, 5)) board[3][2] = Queen.new(false, Vector2(3, 2)) board[3][5] = Pawn.new(false, Vector2(3, 5)) board[4][4] = Assassin.new(false, Vector2(4, 4)) board[3][4] = Pawn.new(false, Vector2(3, 4)) board[4][3] = Mage.new(false, Vector2(4, 3)) board[7][5] = Rook.new(false, Vector2(7, 5)) #potential 'holes', not quite working yet #board[3][3] = '/' draw_board(board_width,board_height) #set up color for opponent's shader, currently set to black opponent_shader.shader = preload("res://assets/shaders/color.gdshader").duplicate() opponent_shader.set_shader_parameter('r', 0.2) opponent_shader.set_shader_parameter('g', 0.2) opponent_shader.set_shader_parameter('b', 0.2) #set up color for selected piece's shader, currently set to red selected_shader.shader = preload("res://assets/shaders/color.gdshader").duplicate() selected_shader.set_shader_parameter('r', 1.0) selected_shader.set_shader_parameter('g', 0.5) selected_shader.set_shader_parameter('b', 0.5) draw_pieces() # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: #dynamically translates loaction of board and pieces to remain in corrent location tile_container.position = translate() shadow_container.position = translate() piece_container.position = translate() #attempt at linear interpolation for smooth movement of pieces# #lerp for smooth movement of pieces if moving: #if piece is not at target position, linearly interpolate to target if selected_piece.position != selected_piece_position: #if within threshold, snap to position to avoid floating point error if selected_piece.position.distance_to(target_position) < 0.1: selected_piece.position = selected_piece_position unselect_piece() moving = false # Snap to exact position else: selected_piece.position = selected_piece.position.lerp(selected_piece_position, 10 * delta) else: moving = false unselect_piece() if selected_piece != null: # If the selected piece is a pawn and it reaches the end of the board, promote it to a queen if selected_piece_position.y == 0: if selected_piece_value is Pawn: if selected_piece_value.is_white: # draw_pieces() selected_piece.queue_free() board[selected_piece_position.x][selected_piece_position.y] = Queen.new(true, selected_piece_position) var piece_scene = preload("res://scenes/Queen.tscn") var piece_instance = piece_scene.instantiate() piece_instance.position = Vector2((selected_piece_position.x * tile_size) + tile_size/2.0, (selected_piece_position.y * tile_size) + tile_size/2.0) piece_instance.z_index = 3 piece_instance.scale = Vector2(1.25, 1.25) # Scale other pieces by 25% piece_container.add_child(piece_instance) explosion_effect.position = piece_instance.position + translate() explosion_effect.restart() # remove_piece(selected_piece_position.x, selected_piece_position.y) func draw_pieces(): #iterate through all tiles, if tile has a piece, #add corresponding piece with correct sprite to piece_container for x in range(board_width): for y in range(board_height): if !is_empty(x, y): var piece_scene if board[x][y] is Pawn: piece_scene = preload("res://scenes/Pawn.tscn") elif board[x][y] is Rook: piece_scene = preload("res://scenes/Rook.tscn") elif board[x][y] is Bishop: piece_scene = preload("res://scenes/Bishop.tscn") elif board[x][y] is Queen: piece_scene = preload("res://scenes/Queen.tscn") elif board[x][y] is King: piece_scene = preload("res://scenes/King.tscn") elif board[x][y] is Knight: piece_scene = preload("res://scenes/Knight.tscn") elif board[x][y] is Assassin: piece_scene = preload("res://scenes/Assassin.tscn") elif board[x][y] is Mage: piece_scene = preload("res://scenes/Mage.tscn") elif board[x][y] is Wizard: piece_scene = preload("res://scenes/Wizard.tscn") if piece_scene: var piece_instance = piece_scene.instantiate() piece_instance.position = Vector2((x * tile_size) + tile_size/2.0, (y * tile_size) + tile_size/2.0) piece_instance.z_index = 3 # Scale the sprite by 25% for all pieces except the king if board[x][y] is King: piece_instance.scale = Vector2(2.0, 2.0) # Scale the king by 50% else: piece_instance.scale = Vector2(1.25, 1.25) # Scale other pieces by 25% # If piece is opponent, add a black shader to piece if board[x][y].is_white == false: var piece_sprite = piece_instance.get_node("Sprite2D") as Sprite2D if piece_sprite: piece_sprite.material = opponent_shader piece_container.add_child(piece_instance) #translates 'home' coordinates ((0,0)) to screen location #used to center board, shadow and pieces at 2/3 of x axis and 1/2 of y axis func translate() -> Vector2: var screen_size = get_viewport().get_visible_rect().size var board_x_offset = float(board_width * tile_size) / 2 var board_y_offset = float(board_height * tile_size) / 2 #places top left corner at 2/3 of x axis and 1/2 of y axis, then translates to center return Vector2(screen_size.x * 2 / 3 - board_x_offset, screen_size.y / 2 - board_y_offset) #color rect version func draw_board(width, height): for x in range(width): for y in range(height): var tile = ColorRect.new() if (x + y)%2 == 0: tile.color = light else: tile.color = dark tile.size = Vector2(tile_size, tile_size) tile.position = Vector2(x * tile_size, y * tile_size) tile_container.add_child(tile) var shadow_shader = ShaderMaterial.new() shadow_shader.shader = preload("res://assets/shaders/shadow.gdshader") var shadow = ColorRect.new() shadow.size = Vector2(tile_size, tile_size) shadow.position = Vector2(x * tile_size + 15, y * tile_size + 15) shadow.material = shadow_shader shadow_container.add_child(shadow) #textured rect version func draw_board_texture_rect(width, height): dark_tile_shader.shader = preload("res://assets/shaders/color.gdshader").duplicate() dark_tile_shader.set_shader_parameter('r', 0.1) dark_tile_shader.set_shader_parameter('g', 0.25) dark_tile_shader.set_shader_parameter('b', 0.6) light_tile_shader.shader = preload("res://assets/shaders/color.gdshader").duplicate() light_tile_shader.set_shader_parameter('r', 0.85) light_tile_shader.set_shader_parameter('g', 0.85) light_tile_shader.set_shader_parameter('b', 0.85) for x in range(width): for y in range(height): if board[x][y] == '/': pass else: var tile = TextureRect.new() tile.texture = preload("res://assets/sprites/tile.png") if (x + y)%2 == 0: tile.material = light_tile_shader else: tile.material = dark_tile_shader tile.size = Vector2(tile_size, tile_size) tile.position = Vector2(x * tile_size, y * tile_size) tile_container.add_child(tile) var shadow_shader = ShaderMaterial.new() shadow_shader.shader = preload("res://assets/shaders/shadow.gdshader") var shadow = ColorRect.new() shadow.size = Vector2(tile_size, tile_size) shadow.position = Vector2(x * tile_size + 15, y * tile_size + 15) shadow.material = shadow_shader shadow_container.add_child(shadow) #detects if mouse events occur within the board func is_on_board(position : Vector2) -> bool: if position.x < (board_width * tile_size) && position.x > 0: if position.y < (board_height * tile_size) && position.y > 0: return true return false func _input(event): if event is InputEventMouseButton: #handles only mouse clicks if !moving: if event.pressed: #translate the mouse click position to 'home' coordinates (start at 0,0) var translated_postition = event.position - translate() #if the click is within the board, proceed if is_on_board(translated_postition): #translates mouse position to array index. #example: (66.5, 99.9) = (1, 1) , (120.0, 12.0) = (2, 0) var mouse_position = Vector2(int(translated_postition.x / tile_size), int(translated_postition.y / tile_size)) var x = mouse_position.x var y = mouse_position.y #if mouse click is a left click, proceed if event.button_index == MOUSE_BUTTON_LEFT: if player_turn: #if the tile is a player's piece, select or deselect it if !is_empty(x,y) && !(is_opponent(x,y)): #if there is a currently selected piece that is on the selected tile, deselect that piece if selected_piece != null: if board[x][y] == selected_piece_value: unselect_piece() else: select_piece(x,y) #otherwise, select the piece else: select_piece(x,y) #if the tile is empty or an opponents piece, move the selected piece to that tile else: move_selected_piece(x,y) else: print("It's not your turn!") if !is_empty(x,y) && is_opponent(x,y): if selected_piece!=null: if board[x][y] == selected_piece_value: unselect_piece() else: select_piece(x,y) else: select_piece(x,y) else: move_selected_piece(x,y) func is_opponent(x,y): if board[x][y] == null: return false else: # If the value of board[x][y] is an opponent piece if selected_piece != null: return board[x][y].is_white != selected_piece_value.is_white else: return !board[x][y].is_white func is_opposite(x,y): if board[x][y] == null: return false else: if selected_piece != null: return board[x][y].is_white == selected_piece_value.is_white else: return board[x][y].is_white func is_empty(x, y): return board[x][y] == null #test funciton to add pieces to board #func add_pawn(x, y): #var piece = TextureRect.new() #piece.position = Vector2(x*tile_size, y*tile_size) #piece.size = Vector2(64,64) #piece.z_index = 3 #piece.texture = preload("res://pawn.png") #piece_container.add_child(piece) func remove_piece(x,y): #iterate through the pieces in piece_container, if that piece is at location x,y, remove it # TODO: Fix bug with promoted Queen, when capturing other pieces, they are not visually removed. for child in piece_container.get_children(): #translate x,y index values to screen coordinates if child.position == Vector2(x*tile_size + 32, y*tile_size + 32): explosion_effect.position = child.position + translate() explosion_effect.restart() var piece = child.duplicate() if is_opponent(x, y): opponent_piece_count += 1 if opponent_piece_count <= 4: piece.position = Vector2(opponent_piece_count - 1, 0) * tile_size elif opponent_piece_count <= 8: piece.position = Vector2(opponent_piece_count - 5, 1) * tile_size elif opponent_piece_count <= 12: piece.position = Vector2(opponent_piece_count - 9, 2) * tile_size else: piece.position = Vector2(opponent_piece_count - 13, 3) * tile_size opponent_pieces.add_child(piece) else: player_piece_count += 1 if player_piece_count <= 4: piece.position = Vector2(player_piece_count - 1, 0) * tile_size elif player_piece_count <= 8: piece.position = Vector2(player_piece_count - 5, 1) * tile_size elif player_piece_count <= 12: piece.position = Vector2(player_piece_count - 9, 2) * tile_size else: piece.position = Vector2(player_piece_count - 13, 3) * tile_size player_pieces.add_child(piece) piece_container.remove_child(child) child.queue_free() break func select_piece(x,y): unselect_piece() #iterate through the pieces in piece_container, if that piece is at location x,y, select it for child in piece_container.get_children(): if child.position == Vector2((x*tile_size)+32, (y*tile_size)+32): selected_piece_position = Vector2(x,y) selected_piece = child selected_piece_value = board[x][y] #add a red coloured shader to the selected piece child.material = selected_shader #highlight the valid moves the selected piece can make highlight_tiles() func unselect_piece(): if selected_piece != null: selected_piece.material = null selected_piece = null selected_piece_value = null remove_highlight() func highlight_tiles(): # Clear the previously selected valid moves remove_highlight() if selected_piece_value is Mage || selected_piece_value is Wizard: valid_moves = selected_piece_value.get_valid_moves(board, selected_piece_position) valid_moves += selected_piece_value.get_valid_shots(board, selected_piece_position) else: valid_moves = selected_piece_value.get_valid_moves(board, selected_piece_position) for child in tile_container.get_children(): if child.position / tile_size in valid_moves: child.modulate = Color(1, 0.5, 0.5) else: child.modulate = Color(1, 1, 1) func remove_highlight(): #remove all highlights from tiles in tile_container for child in tile_container.get_children(): child.modulate = Color(1,1,1) valid_moves = [] func move_selected_piece(x,y): #can only move a piece if selected_piece exists if selected_piece != null: #can only move to tiles in valid_moves if Vector2(x,y) in valid_moves: if selected_piece_value is Mage || selected_piece_value is Wizard: if abs(selected_piece_position.x - x) <= 1 and abs(selected_piece_position.y - y) <= 1: if is_empty(x, y): move_piece(x, y) else: shoot_projectile(x, y) else: shoot_projectile(x, y) # check for if assassin is moving behind the opponent's pieces elif selected_piece_value is Assassin: if selected_piece_value.is_white: if is_opponent(x, y + 1): remove_piece(x, y + 1) else: if is_opponent(x, y - 1): remove_piece(x, y - 1) move_piece(x, y) else: move_piece(x, y) else: unselect_piece() remove_highlight() func move_piece(x, y): # If tile has opponent piece, remove piece if is_opponent(x, y): remove_piece(x, y) # Clear the selected_piece's previous position board[selected_piece_position.x][selected_piece_position.y] = null # Update the board with the new position board[x][y] = selected_piece_value # Update selected_piece's screen coordinate position selected_piece_position = Vector2((x * tile_size) + tile_size / 2.0, (y * tile_size) + tile_size / 2.0) target_position = selected_piece_position moving = true valid_moves = [] func shoot_projectile(x: int, y: int): remove_piece(x, y) board[x][y] = null unselect_piece() explosion_effect.color = Color(1, 0, 1) explosion_effect.position = Vector2(x, y) * tile_size + translate() + Vector2(tile_size / 2.0, tile_size / 2.0) explosion_effect.restart() # player_turn = !player_turn # Shop Stuff func _on_shop_button_pressed(): shop.visible = !shop.visible update_gold_display() # Pass current gold to shop piece_container.visible = !piece_container.visible shadow_container.visible = !shadow_container.visible tile_container.visible = !tile_container.visible func _on_purchase_attempted(item_data: Dictionary, shop_item: Node): # Check gold here where we have access to the real value if player_gold >= item_data["price"]: # Deduct gold and mark the item as purchased player_gold -= item_data["price"] item_data["purchased"] = true # Show "Sold Out" overlay and disable button shop_item.get_node("MarginContainer/VBoxContainer/SoldOutLabel").visible = true shop_item.get_node("Overlay").visible = true shop_item.get_node("MarginContainer/VBoxContainer/BuyButton").disabled = true # Creates an array of piece / upgrade names. var piece_names = shop.shop_items["pieces"].map(func(item): return item["name"]) var upgrade_names = shop.shop_items["upgrades"].map(func(item): return item["name"]) # checks if the purchased item’s name exists in the list of pieces or upgrades. if item_data["name"] in piece_names: print("Purchased piece: ", item_data["name"]) purchased_pieces.append(item_data) elif item_data["name"] in upgrade_names: print("Purchased upgrade: ", item_data["name"]) purchased_upgrades.append(item_data) # Update UI and print debug info update_gold_display() else: # TODO: Play sound / animation for not enough gold print("Not enough gold!") func _on_gold_spent(amount: int): player_gold -= amount gold_display.text = "Gold: %d" % player_gold # Update the display print("Gold spent: %d. Remaining gold: %d" % [amount, player_gold]) func update_gold_display(): gold_display.text = "Gold: %d" % player_gold func _on_next_round_requested(): print("Next round requested!") shop.visible = false # Hide the shop piece_container.visible = true # Show the board shadow_container.visible = true tile_container.visible = true print("Player items in purchased_pieces: ", purchased_pieces) print("Player upgrades in purchased_upgrades: ", purchased_upgrades) func set_gold_display_position(): # Set anchors to a percentage of the parent container's size gold_display.anchor_left = 0.2 gold_display.anchor_top = 0.0 gold_display.anchor_right = 1.0 gold_display.anchor_bottom = 0.1 func _on_viewport_size_changed(): if shop: var viewport_size = get_viewport().get_visible_rect().size shop.position = viewport_size / 2