How can i make an AI that form words?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Gonz4 L

I’m making a scrabble rpg and the enemy should attack forming words as the player.
Lettertile.gd:

extends Node2D
class_name LetterTile

# Letter sprites by Kenney.nl
# https://opengameart.org/content/letter-tiles

var current_tile = 0
var letter_value = ""
var letter_points = 0
var box_position: Vector2
signal tile_clicked

func _ready():
	hide()
	randomize()
	$HiddenTimer.wait_time = rand_range(0.0, 0.5)
	$HiddenTimer.start()
	letter_value = Helpers.get_next_letter()
	current_tile = get_frame_from_letter(letter_value)
	letter_points = get_points_value(current_tile)
	$Tiles.frame = current_tile

func _on_ClickableArea_input_event(_viewport, event, _shape_idx):
	if event is InputEventMouseButton \
	and event.button_index == BUTTON_LEFT \
	and event.is_pressed():
		emit_signal("tile_clicked")

func _on_Clickable_pressed():
	emit_signal("tile_clicked")

func _on_HiddenTimer_timeout():
	show()

func get_points_value(letter):
	match letter:
		0:
			return 4
		1:
			return 1
		2:
			return 5
		3:
			return 2
		4:
			return 8
		5:
			return 10
		6:
			return 8
		7:
			return 3
		8:
			return 4
		9:
			return 3
		10:
			return 1
		11:
			return 3
		12:
			return 4
		13:
			return 1
		14:
			return 4
		15:
			return 1
		16:
			return 1
		17:
			return 1
		18:
			return 2
		19:
			return 1
		20:
			return 1
		21:
			return 3
		22:
			return 4
		23:
			return 1
		24:
			return 1
		25:
			return 10
		_:
			return 0

func get_frame_from_letter(letter):
	match letter:
		"Y":
			return 0
		"R":
			return 1
		"K":
			return 2
		"D":
			return 3
		"X":
			return 4
		"Q":
			return 5
		"J":
			return 6
		"C":
			return 7
		"W":
			return 8
		"P":
			return 9
		"I":
			return 10
		"B":
			return 11
		"V":
			return 12
		"O":
			return 13
		"H":
			return 14
		"A":
			return 15
		"U":
			return 16
		"N":
			return 17
		"G":
			return 18
		"E":
			return 19
		"T":
			return 20
		"M":
			return 21
		"F":
			return 22
		"L":
			return 23
		"S":
			return 24
		"Z":
			return 25
		"":
			return -1

func get_letter_value(tile_value):
	match tile_value:
		0:
			return "Y"
		1:
			return "R"
		2:
			return "K"
		3:
			return "D"
		4:
			return "X"
		5:
			return "Q"
		6:
			return "J"
		7:
			return "C"
		8:
			return "W"
		9:
			return "P"
		10:
			return "I"
		11:
			return "B"
		12:
			return "V"
		13:
			return "O"
		14:
			return "H"
		15:
			return "A"
		16:
			return "U"
		17:
			return "N"
		18:
			return "G"
		19:
			return "E"
		20:
			return "T"
		21:
			return "M"
		22:
			return "F"
		23:
			return "L"
		24:
			return "S"
		25:
			return "Z"
		_:
			return ""

Battle.gd:

func spawn_new_letters():
	for i in range(4):
		for j in range(4):
			var tile:LetterTile = Letter.instance()
			$LetterBox.add_child(tile)
			# warning-ignore:return_value_discarded
			tile.connect("tile_clicked", self, "on_tile_clicked", [tile])
			tile.position = Vector2(i * TILESIZE, j * TILESIZE) + Vector2(4 * i, 4 * j)
			tile.box_position = tile.position

func on_tile_clicked(tile:LetterTile):
	var start_pos = tile.global_position
	if tile.get_parent().name == "LetterBox":
		$LetterBox.remove_child(tile)
		$Word.add_child(tile)
		tile.global_position = start_pos
		current_word += tile.letter_value
		total_points += tile.letter_points
		if current_word.length() > 0:
			$UIButtons.show()
	else:
		var tile_position = tile.get_position_in_parent()
		$Word.remove_child(tile)
		$LetterBox.add_child(tile)
		$Tween.interpolate_property(tile,"position", start_pos - $LetterBox.position, tile.box_position,0.2,Tween.TRANS_LINEAR)
		current_word = current_word.substr(0, tile_position) + current_word.substr(tile_position + 1, current_word.length())
		total_points -= tile.letter_points
		if current_word.length() == 0:
			$UIButtons.hide()
	arrange_word_tiles()
	check_for_valid_word()
	set_hint_text()
	MusicPlayer.play_sfx("letter_click")

func clear_all_letters():
	for tile in $LetterBox.get_children():
		tile.queue_free()
	for tile in $Word.get_children():
		tile.queue_free()

func check_for_valid_word():
	if Helpers.is_word_valid(current_word):
		confirm_button.disabled = false
	else:
		confirm_button.disabled = true

...

:bust_in_silhouette: Reply From: Thomas Karcher

For that, you’d first need an algorithm finding words from a dictionary matching your available letters, like the code below, which prints all words from my_dictionary matching the available letters:

var my_dictionary: PoolStringArray = [
	"about", "erase", "laser", "real", "really", "relax", "taxi"
]

func _ready():
	var available_letters: Array = ["Y", "A", "L", "S", "E", "R", "L", "X"]
	print(get_matching_words(available_letters))
	
func get_matching_words(available_letters: Array) -> Array:
	var result : Array = []
	for word in my_dictionary:
		var check_array : Array = available_letters.duplicate()
		var is_match : bool = true
		for letter in word.to_upper():
			var letter_pos = check_array.find(letter)
			if letter_pos == -1:
				is_match = false
				break
			else:
				check_array.remove(letter_pos)
		if is_match: result.append(word)
	return result

Your enemy could then use one of these words (a random one, the longest or the best one fitting to your scrabble board).

I might suggest the addition of a break after a letter match fails as it’ll make the search more efficient. So, this…

        if letter_pos == -1:
            is_match = false
            break # <---------- add this...

There’s no sense in scanning the remaining letters in the current word once you know you can’t create it…

jgodfrey | 2020-11-20 20:58

Added, thanks. (Although I doubt that checking a couple of additional letters really causes any performance issues in a scrabble game ;-))

[edit] Thinking about it, with a dictionary containing thousands of words this break could indeed save a considerable amount of time. Thanks again! [/edit]

Thomas Karcher | 2020-11-20 21:27