In this tutorial, we are going to show to to implement word search generator project with Python Turtle. We will break down the project into a 6-step process.
Step 1. Drawing the Grid
Drawing the grid above can be achieved by drawing a number of horizontal and vertical lines. Since we want our word search generator to draw any number of cells, we will use a number N to represent the NxN dimension of the grid. The following is the code that draws this grid. Please read the comments to help you understand the code better. Try changing values for N or CX or CY to fit your situation better.
import turtle
screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Word Search Generator - PythonTurtle.Academy")
turtle.speed(0)
turtle.tracer(0,0)
turtle.hideturtle()
# constants
N = 20 # number of rows and columns
CX = -450 # x coordinate of lower left corner of the puzzle
CY = -450 # y coordinate of lower left corner
L = -CX*2 # length of the puzzle
def draw_grid():
# draw horizontal lines
turtle.color('light gray')
y = CY
turtle.seth(0)
for i in range(N+1): # need to draw n+1 lines to form n blocks
turtle.up()
turtle.goto(CX,y)
turtle.down()
turtle.fd(L)
y += L/N
# draw horizontal lines
x = CX
turtle.seth(90)
for i in range(N+1):
turtle.up()
turtle.goto(x,CY)
turtle.down()
turtle.fd(L)
x += L/N
draw_grid()
turtle.update()
Step 2: Creating, Initializing, and Drawing the Letter Matrix
In this step, we are going to create a variable called letter_matrix to represent NxN letters in the grid cells. This variable is a list of lists: it contains a list of rows, each of which is list of letters. We initialize the content of each cell to question mark ‘?’ to show that we have not set the right values to these cells yet. Function create_letter_matrix() creates and initializes letter_matrix. Function draw_letter_matrix() draw the letters in letter_matrix to the screen with Turtle commands. The following is the code for step 2.
import turtle
screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Word Search Generator - PythonTurtle.Academy")
turtle.speed(0)
turtle.tracer(0,0)
turtle.hideturtle()
# constants
N = 20 # number of rows and columns
CX = -450 # x coordinate of lower left corner of the puzzle
CY = -450 # y coordinate of lower left corner
L = -CX*2 # length of the puzzle
# variables
letter_matrix = [] # list of lists (NxN) letters in the grid
def draw_grid():
# draw horizontal lines
turtle.color('light gray')
y = CY
turtle.seth(0)
for i in range(N+1): # need to draw n+1 lines to form n blocks
turtle.up()
turtle.goto(CX,y)
turtle.down()
turtle.fd(L)
y += L/N
# draw horizontal lines
x = CX
turtle.seth(90)
for i in range(N+1):
turtle.up()
turtle.goto(x,CY)
turtle.down()
turtle.fd(L)
x += L/N
# creates NxN letter matrix with all '?'
def create_letter_matrix():
global letter_matrix
for i in range(N): # rows
letter_row = []
for j in range(N): # cols
letter_row.append('?') # Add '?' to the list
letter_matrix.append(letter_row) # Add a row list to letter_matrix list
# draw NxN letter matrix
def draw_letter_matrix():
turtle.up()
turtle.color('black')
y = CY + L/(2*N) - 8 # Try to align letter to the center of each square box
for i in range(N): # rows
x = CX + L/(2*N)
for j in range(N): # cols
turtle.goto(x,y)
turtle.write(letter_matrix[i][j],align='center',font=('courier',12,'normal'))
x += L/N
y += L/N
draw_grid()
create_letter_matrix()
draw_letter_matrix()
turtle.update()
Step 3: Adding a Word from Left to Right into Letter Matrix
This step is the probably the hardest step in this project. We want to add a word into the letter matrix from left to right with random starting position. We can start from any row but not from any column. It is obvious that we can’t set the starting column to be very far to the right because there may not be space to hold the word. So, we will need to know the length of the word so that the starting column plus the length of the word won’t exceed the value N.
But, since we are writing many words into the matrix, it is possible that some of cells from the starting position and next positions on the right may contain letters from another word. We can’t overwrite letters from another word, but if the letters happen to be same we can overwrite, which is a desirable overlapping effect.
The following is the source code for step 3. We created two new functions: add_word_right(w) and can_add_word_right(row,col,w). The first functions adds a word w to the letter matrix from left to right. It calls second function can_add_word_right(row,col,w) to see if it is okay to add a word from starting position (row, col). If it is not okay, the first function try a different random starting position and tries again until can_add_word_right(row,col,w) return True. Try to call add_word_right(w) function with several words and see if it add words correctly by calling draw_letter_matrix() afterwards.
import turtle
import random
screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Word Search Generator - PythonTurtle.Academy")
turtle.speed(0)
turtle.tracer(0,0)
turtle.hideturtle()
# constants
N = 10 # number of rows and columns
CX = -450 # x coordinate of lower left corner of the puzzle
CY = -450 # y coordinate of lower left corner
L = -CX*2 # length of the puzzle
# variables
letter_matrix = [] # list of lists (NxN) letters in the grid
def draw_grid():
# draw horizontal lines
turtle.color('light gray')
y = CY
turtle.seth(0)
for i in range(N+1): # need to draw n+1 lines to form n blocks
turtle.up()
turtle.goto(CX,y)
turtle.down()
turtle.fd(L)
y += L/N
# draw horizontal lines
x = CX
turtle.seth(90)
for i in range(N+1):
turtle.up()
turtle.goto(x,CY)
turtle.down()
turtle.fd(L)
x += L/N
# creates NxN letter matrix with all '?'s
def create_letter_matrix():
global letter_matrix
for i in range(N): # rows
letter_row = []
for j in range(N): # cols
letter_row.append('?') # Add '?' to the list
letter_matrix.append(letter_row) # Add a row list to letter_matrix list
# draw NxN letter matrix
def draw_letter_matrix():
turtle.up()
turtle.color('black')
y = CY + L/(2*N) - 8 # Try to align letter to the center of each square box
for i in range(N): # rows
x = CX + L/(2*N)
for j in range(N): # cols
turtle.goto(x,y)
turtle.write(letter_matrix[i][j],align='center',font=('courier',12,'normal'))
x += L/N
y += L/N
# put a single word 'w' into the letter_matrix with 'right' direction and random position
def add_word_right(w):
global letter_matrix
w_length = len(w) # len() function returns the length of the string w
row = random.randint(0,N-1) # all starting rows are ok
col = random.randint(0,N-w_length) # since filling to right, starting col can't exceed the N-w_length
# We need to check if (row,col) is good starting position
# It is possible that some of the cells is already occupied by other words
# To avoid making a single function over complicated, let's make another function to check this
while can_add_word_right(row,col,w) == False: # keep trying to find new location if not ok
row = random.randint(0,N-1) # all starting rows are ok
col = random.randint(0,N-w_length) # since filling to right, starting col can't exceed the N-w_length
# at this point, (row,col) is a good candidate to fill in the letters
for i in range(w_length):
letter_matrix[row][col+i] = w[i]
# checks if we can add word 'w' with starting position (row,col). Return True or False
def can_add_word_right(row,col,w):
global letter_matrix
for i in range(len(w)):
# It's okay if a cell is not set (still '?')
# It's also okay a cell is set but it's set to the same letter as the i'th letter in the word w
# This will allow words to overlap - a desirable effect
if letter_matrix[row][col+i] != '?' and letter_matrix[row][col+i] != w[i]: # not okay conditions
return False
return True # not okay condition never happened, so return okay
draw_grid()
create_letter_matrix()
add_word_right('PYTHON')
add_word_right('TURTLE')
draw_letter_matrix()
turtle.update()
Step 4: Adding More Directions
In step 3, we are able to add a word from left to right into the matrix. Add other remaining directions into your program and test. Knowing how to do from left to right, it should be straightforward to add more directions. The following adds left, up, and down. You can add four other diagonal directions yourself.
import turtle
import random
screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Word Search Generator - PythonTurtle.Academy")
turtle.speed(0)
turtle.tracer(0,0)
turtle.hideturtle()
# constants
N = 15 # number of rows and columns
CX = -450 # x coordinate of lower left corner of the puzzle
CY = -450 # y coordinate of lower left corner
L = -CX*2 # length of the puzzle
# variables
letter_matrix = [] # list of lists (NxN) letters in the grid
def draw_grid():
# draw horizontal lines
turtle.color('light gray')
y = CY
turtle.seth(0)
for i in range(N+1): # need to draw n+1 lines to form n blocks
turtle.up()
turtle.goto(CX,y)
turtle.down()
turtle.fd(L)
y += L/N
# draw horizontal lines
x = CX
turtle.seth(90)
for i in range(N+1):
turtle.up()
turtle.goto(x,CY)
turtle.down()
turtle.fd(L)
x += L/N
# creates NxN letter matrix with all '?'s
def create_letter_matrix():
global letter_matrix
for i in range(N): # rows
letter_row = []
for j in range(N): # cols
letter_row.append('?') # Add '?' to the list
letter_matrix.append(letter_row) # Add a row list to letter_matrix list
# draw NxN letter matrix
def draw_letter_matrix():
turtle.up()
turtle.color('black')
y = CY + L/(2*N) - 8 # Try to align letter to the center of each square box
for i in range(N): # rows
x = CX + L/(2*N)
for j in range(N): # cols
turtle.goto(x,y)
turtle.write(letter_matrix[i][j],align='center',font=('courier',12,'normal'))
x += L/N
y += L/N
# put a single word 'w' into the letter_matrix with 'right' direction and random position
def add_word_right(w):
global letter_matrix
w_length = len(w) # len() function returns the length of the string w
row = random.randint(0,N-1) # all starting rows are ok
col = random.randint(0,N-w_length) # since filling to right, starting col can't exceed the N-w_length
# We need to check if (row,col) is good starting position
# It is possible that some of the cells is already occupied by other words
# To avoid making a single function over complicated, let's make another function to check this
while can_add_word_right(row,col,w) == False: # keep trying to find new location if not ok
row = random.randint(0,N-1) # all starting rows are ok
col = random.randint(0,N-w_length) # since filling to right, starting col can't exceed the N-w_length
# at this point, (row,col) is a good candidate to fill in the letters
for i in range(w_length):
letter_matrix[row][col+i] = w[i]
# checks if we can add word 'w' with starting position (row,col). Return True or False
def can_add_word_right(row,col,w):
global letter_matrix
for i in range(len(w)):
# It's okay if a cell is not set (still '?')
# It's also okay a cell is set but it's set to the same letter as the i'th letter in the word w
# This will allow words to overlap - a desirable effect
if letter_matrix[row][col+i] != '?' and letter_matrix[row][col+i] != w[i]: # not okay conditions
return False
return True # not okay condition never happened, so return okay
def add_word_left(w):
global letter_matrix
w_length = len(w)
row = random.randint(0,N-1)
col = random.randint(w_length-1,N-1)
while can_add_word_left(row,col,w) == False:
row = random.randint(0,N-1)
col = random.randint(w_length-1,N-1)
for i in range(w_length):
letter_matrix[row][col-i] = w[i]
def can_add_word_left(row,col,w):
global letter_matrix
for i in range(len(w)):
if letter_matrix[row][col-i] != '?' and letter_matrix[row][col-i] != w[i]:
return False
return True
def add_word_up(w):
global letter_matrix
w_length = len(w)
row = random.randint(0,N-w_length)
col = random.randint(0,N-1)
while can_add_word_up(row,col,w) == False:
row = random.randint(0,N-w_length)
col = random.randint(0,N-1)
for i in range(w_length):
letter_matrix[row+i][col] = w[i]
def can_add_word_up(row,col,w):
global letter_matrix
for i in range(len(w)):
if letter_matrix[row+i][col] != '?' and letter_matrix[row+i][col] != w[i]:
return False
return True
def add_word_down(w):
global letter_matrix
w_length = len(w)
row = random.randint(w_length-1,N-1)
col = random.randint(0,N-1)
while can_add_word_down(row,col,w) == False:
row = random.randint(w_length-1,N-1)
col = random.randint(0,N-1)
for i in range(w_length):
letter_matrix[row-i][col] = w[i]
def can_add_word_down(row,col,w):
global letter_matrix
for i in range(len(w)):
if letter_matrix[row-i][col] != '?' and letter_matrix[row-i][col] != w[i]:
return False
return True
draw_grid()
create_letter_matrix()
add_word_left('PYTHON')
add_word_right('TURTLE')
add_word_up('RANDOM')
add_word_down('PROGRAMMING')
draw_letter_matrix()
turtle.update()
Step 5: List of Words and Randomly Choosing Directions
Let’s make the whole process more automated. Instead of adding words one by one, let’s create a list of words that you want to add to the letter matrix. Then we will add them using a loop. The question is: which direction should we use to the add the word? We can also randomize this using this method:
1. Choose a random direction from a list of directions.
2. If random direction is ‘right’ call add add_word_right() function, if it is ‘left’ call add_word_right() etc.
The following is the code for this step. We created a new list variable word_list with a few words in it. You can replace them with your own words. We also created a new function called add_all_words(), which adds all the words in word_list into the letter matrix. We created a list of four direction strings and randomly chooses one of them. Depending on direction string different functions are called. If you have created 8 directions, you will need to modify this part to call all 8 directions.
import turtle
import random
screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Word Search Generator - PythonTurtle.Academy")
turtle.speed(0)
turtle.tracer(0,0)
turtle.hideturtle()
# constants
N = 15 # number of rows and columns
CX = -450 # x coordinate of lower left corner of the puzzle
CY = -450 # y coordinate of lower left corner
L = -CX*2 # length of the puzzle
# variables
letter_matrix = [] # list of lists (NxN) letters in the grid
word_list = [ 'PYTHON', 'PROGRAMMING', 'TURTLE', 'RANDOM', 'COLOR', 'RECURSION', 'LOOPS', 'ANIMATION', 'GAMES' ]
def draw_grid():
# draw horizontal lines
turtle.color('light gray')
y = CY
turtle.seth(0)
for i in range(N+1): # need to draw n+1 lines to form n blocks
turtle.up()
turtle.goto(CX,y)
turtle.down()
turtle.fd(L)
y += L/N
# draw horizontal lines
x = CX
turtle.seth(90)
for i in range(N+1):
turtle.up()
turtle.goto(x,CY)
turtle.down()
turtle.fd(L)
x += L/N
# creates NxN letter matrix with all '?'s
def create_letter_matrix():
global letter_matrix
for i in range(N): # rows
letter_row = []
for j in range(N): # cols
letter_row.append('?') # Add '?' to the list
letter_matrix.append(letter_row) # Add a row list to letter_matrix list
# draw NxN letter matrix
def draw_letter_matrix():
turtle.up()
turtle.color('black')
y = CY + L/(2*N) - 8 # Try to align letter to the center of each square box
for i in range(N): # rows
x = CX + L/(2*N)
for j in range(N): # cols
turtle.goto(x,y)
turtle.write(letter_matrix[i][j],align='center',font=('courier',12,'normal'))
x += L/N
y += L/N
# put a single word 'w' into the letter_matrix with 'right' direction and random position
def add_word_right(w):
global letter_matrix
w_length = len(w) # len() function returns the length of the string w
row = random.randint(0,N-1) # all starting rows are ok
col = random.randint(0,N-w_length) # since filling to right, starting col can't exceed the N-w_length
# We need to check if (row,col) is good starting position
# It is possible that some of the cells is already occupied by other words
# To avoid making a single function over complicated, let's make another function to check this
while can_add_word_right(row,col,w) == False: # keep trying to find new location if not ok
row = random.randint(0,N-1) # all starting rows are ok
col = random.randint(0,N-w_length) # since filling to right, starting col can't exceed the N-w_length
# at this point, (row,col) is a good candidate to fill in the letters
for i in range(w_length):
letter_matrix[row][col+i] = w[i]
# checks if we can add word 'w' with starting position (row,col). Return True or False
def can_add_word_right(row,col,w):
global letter_matrix
for i in range(len(w)):
# It's okay if a cell is not set (still '?')
# It's also okay a cell is set but it's set to the same letter as the i'th letter in the word w
# This will allow words to overlap - a desirable effect
if letter_matrix[row][col+i] != '?' and letter_matrix[row][col+i] != w[i]: # not okay conditions
return False
return True # not okay condition never happened, so return okay
def add_word_left(w):
global letter_matrix
w_length = len(w)
row = random.randint(0,N-1)
col = random.randint(w_length-1,N-1)
while can_add_word_left(row,col,w) == False:
row = random.randint(0,N-1)
col = random.randint(w_length-1,N-1)
for i in range(w_length):
letter_matrix[row][col-i] = w[i]
def can_add_word_left(row,col,w):
global letter_matrix
for i in range(len(w)):
if letter_matrix[row][col-i] != '?' and letter_matrix[row][col-i] != w[i]:
return False
return True
def add_word_up(w):
global letter_matrix
w_length = len(w)
row = random.randint(0,N-w_length)
col = random.randint(0,N-1)
while can_add_word_up(row,col,w) == False:
row = random.randint(0,N-w_length)
col = random.randint(0,N-1)
for i in range(w_length):
letter_matrix[row+i][col] = w[i]
def can_add_word_up(row,col,w):
global letter_matrix
for i in range(len(w)):
if letter_matrix[row+i][col] != '?' and letter_matrix[row+i][col] != w[i]:
return False
return True
def add_word_down(w):
global letter_matrix
w_length = len(w)
row = random.randint(w_length-1,N-1)
col = random.randint(0,N-1)
while can_add_word_down(row,col,w) == False:
row = random.randint(w_length-1,N-1)
col = random.randint(0,N-1)
for i in range(w_length):
letter_matrix[row-i][col] = w[i]
def can_add_word_down(row,col,w):
global letter_matrix
for i in range(len(w)):
if letter_matrix[row-i][col] != '?' and letter_matrix[row-i][col] != w[i]:
return False
return True
def add_all_words():
global word_list
for w in word_list:
# randomly decided on 4 directions to go
direction = random.choice(['left','right','up','down'])
if direction == 'left':
add_word_left(w)
elif direction == 'right':
add_word_right(w)
elif direction == 'up':
add_word_up(w)
else:
add_word_down(w)
draw_grid()
create_letter_matrix()
add_all_words()
draw_letter_matrix()
turtle.update()
Step 6: Randomize Empty Cells
There is still one more step to do. For the empty cells (cells with question mark ?), we need to use random alphabet to replace them. Simple trick is to create a string of the alphabets from A to Z and choose a random index of this string and pick the alphabet in the index to replace the empty cell. The following is the complete code for this project:
import turtle
import random
screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Word Search Generator - PythonTurtle.Academy")
turtle.speed(0)
turtle.tracer(0,0)
turtle.hideturtle()
# constants
N = 15 # number of rows and columns
CX = -450 # x coordinate of lower left corner of the puzzle
CY = -450 # y coordinate of lower left corner
L = -CX*2 # length of the puzzle
# variables
letter_matrix = [] # list of lists (NxN) letters in the grid
word_list = [ 'PYTHON', 'PROGRAMMING', 'TURTLE', 'RANDOM', 'COLOR', 'RECURSION', 'LOOPS', 'ANIMATION', 'GAMES' ]
def draw_grid():
# draw horizontal lines
turtle.color('light gray')
y = CY
turtle.seth(0)
for i in range(N+1): # need to draw n+1 lines to form n blocks
turtle.up()
turtle.goto(CX,y)
turtle.down()
turtle.fd(L)
y += L/N
# draw horizontal lines
x = CX
turtle.seth(90)
for i in range(N+1):
turtle.up()
turtle.goto(x,CY)
turtle.down()
turtle.fd(L)
x += L/N
# creates NxN letter matrix with all '?'s
def create_letter_matrix():
global letter_matrix
for i in range(N): # rows
letter_row = []
for j in range(N): # cols
letter_row.append('?') # Add '?' to the list
letter_matrix.append(letter_row) # Add a row list to letter_matrix list
# draw NxN letter matrix
def draw_letter_matrix():
turtle.up()
turtle.color('black')
y = CY + L/(2*N) - 8 # Try to align letter to the center of each square box
for i in range(N): # rows
x = CX + L/(2*N)
for j in range(N): # cols
turtle.goto(x,y)
turtle.write(letter_matrix[i][j],align='center',font=('courier',12,'normal'))
x += L/N
y += L/N
# put a single word 'w' into the letter_matrix with 'right' direction and random position
def add_word_right(w):
global letter_matrix
w_length = len(w) # len() function returns the length of the string w
row = random.randint(0,N-1) # all starting rows are ok
col = random.randint(0,N-w_length) # since filling to right, starting col can't exceed the N-w_length
# We need to check if (row,col) is good starting position
# It is possible that some of the cells is already occupied by other words
# To avoid making a single function over complicated, let's make another function to check this
while can_add_word_right(row,col,w) == False: # keep trying to find new location if not ok
row = random.randint(0,N-1) # all starting rows are ok
col = random.randint(0,N-w_length) # since filling to right, starting col can't exceed the N-w_length
# at this point, (row,col) is a good candidate to fill in the letters
for i in range(w_length):
letter_matrix[row][col+i] = w[i]
# checks if we can add word 'w' with starting position (row,col). Return True or False
def can_add_word_right(row,col,w):
global letter_matrix
for i in range(len(w)):
# It's okay if a cell is not set (still '?')
# It's also okay a cell is set but it's set to the same letter as the i'th letter in the word w
# This will allow words to overlap - a desirable effect
if letter_matrix[row][col+i] != '?' and letter_matrix[row][col+i] != w[i]: # not okay conditions
return False
return True # not okay condition never happened, so return okay
def add_word_left(w):
global letter_matrix
w_length = len(w)
row = random.randint(0,N-1)
col = random.randint(w_length-1,N-1)
while can_add_word_left(row,col,w) == False:
row = random.randint(0,N-1)
col = random.randint(w_length-1,N-1)
for i in range(w_length):
letter_matrix[row][col-i] = w[i]
def can_add_word_left(row,col,w):
global letter_matrix
for i in range(len(w)):
if letter_matrix[row][col-i] != '?' and letter_matrix[row][col-i] != w[i]:
return False
return True
def add_word_up(w):
global letter_matrix
w_length = len(w)
row = random.randint(0,N-w_length)
col = random.randint(0,N-1)
while can_add_word_up(row,col,w) == False:
row = random.randint(0,N-w_length)
col = random.randint(0,N-1)
for i in range(w_length):
letter_matrix[row+i][col] = w[i]
def can_add_word_up(row,col,w):
global letter_matrix
for i in range(len(w)):
if letter_matrix[row+i][col] != '?' and letter_matrix[row+i][col] != w[i]:
return False
return True
def add_word_down(w):
global letter_matrix
w_length = len(w)
row = random.randint(w_length-1,N-1)
col = random.randint(0,N-1)
while can_add_word_down(row,col,w) == False:
row = random.randint(w_length-1,N-1)
col = random.randint(0,N-1)
for i in range(w_length):
letter_matrix[row-i][col] = w[i]
def can_add_word_down(row,col,w):
global letter_matrix
for i in range(len(w)):
if letter_matrix[row-i][col] != '?' and letter_matrix[row-i][col] != w[i]:
return False
return True
# add all words in word_list into the letter_matrix randomly
def add_all_words():
global word_list
for w in word_list:
# randomly decided on 4 directions to go
direction = random.choice(['left','right','up','down'])
if direction == 'left':
add_word_left(w)
elif direction == 'right':
add_word_right(w)
elif direction == 'up':
add_word_up(w)
else:
add_word_down(w)
# randomly put alphabet letters into cells that is still '?'
def randomize_empty_cells():
global letter_matrix
alphabets = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' # put 26 letters in a string
for i in range(N):
for j in range(N):
if letter_matrix[i][j] == '?': # this cell is still empty
letter_matrix[i][j] = alphabets[random.randrange(26)] # pick up random index in alphabet string
draw_grid()
create_letter_matrix()
add_all_words()
randomize_empty_cells()
draw_letter_matrix()
turtle.update()