Slight nitpick, but I don't like using the word array in Python. They may have array-like syntax from languages like C, but strictly speaking they aren't arrays.
Sanitize your inputs. If a word contains _ , then I suspect your program will not work as intended. As an addendum, don't use sentinel values that could actually be valid. A better option here would be to store blanks as
None
.Refrain from using exceptions unless your case is truly exceptional. In here, it's not very uncommon for a word to conflict with another letter. In this case, performance isn't going to matter but it may in a more CPU-intensive program.
It's quite easy for your program to enter an infinite loop especially if the word list is large. This is because it won't be able to fit all words into the grid and then it'll get stuck. Ideally, you would want to have a way to find a random location amongst those that already work. We don't want to rely on RNG.
Instead of picking a row, column at random and hoping it works, make a generator that yields all locations and orientations that do work for the current word. For this to be correct, it needs to be able to detect when an a grid with a word list is truly impossible. We can use a depth-first search to detect this. The following is pseudo-code on how I would implement it.
def placements(word, grid):
Yields the location, orientation that works for word on the grid
%# Fills in a valid configuration for your grid using DFS
def generate(grid, words, row, col):
%# No more words left to read, we're done
if len(words) == 0:
return true
places = list(placements(word[0], grid))
%# Means current branch won't work !
if len(places) == 0:
return false
random.shuffle(places)
for (location, orientation) in places:
cpy = deepcopy(grid)
Place word[0] into cpy at its location, orientation
if generate(cpy, words[1:], row, col):
grid = cpy
return true
return false