Table of Contents
PyBog
Let's talk about PyBog. The beginning should be easy at this point:
main.py
from myrandom import * from bog import * def main(): # First, we declare all the main variables # at the start of the program. list = [] # list of words for word search bog = make_bog() # the game board # Here, we will try to put five words into 'list', while len(list) < 5: a = random_n_3() if a not in list: # but only if they are not already there. list.append(a) # shuffle the list random.shuffle(list) # Try to fit the word on the board. #gamelist = [] #for word in list: # if not try_put_bog(bog, word, 1000): # gamelist.append(word) #list = gamelist list = [x for x in list if try_put_bog(bog, x, 1000)] fill_bog(bog) # fill up with random letters print_bog(bog) # display the board print(" ".join(list)) # display the words to find if __name__ == "__main__": main()
Look carefully at the code which iterates over the list, and uses a temporary variable, “gamelist”. This can be replaced by the code:
list = [x for x in list if try_put_bog(bog, x, 1000)]
This is a python specific way of filtering a list. You can use either method as you prefer. The longer approach works in most languages, but is considered clunky if a filtering method is available. However, thinking in this way is an important prerequisite in understanding algorithms in general, so you should understand and appreciate the first way even if you are using Python. It is important to get used to moving data around and processing it. In-place processing such as the 'one line solution' can sometimes lead to strange and unforseen results.
The real magic happens in the bog.py file. But first let's look at myrandom.
myrandom.py
import random def random_n_3(): nouns = [ "car", "dog", "cat", "man", "sun", "eye", "pen", "cup", "leg", "hat", "sky", "bus", "bed", "fly", "key", "map", "gun", "bag", "job", "boy", "top", "lip", "son", "toy", "law", "art", "box", "ear", "cow", "ice", "sea", "rat", "egg", "tie", "pie", "pig", "bee" ] n = random.choice(nouns) return n
This is just a file that gives us a list of random words. It's data, so I put it in a separate file.
I do not recommend you mix code and data. If you have a lot of data, try to separate it from the algorithms in your code. That way you can change the way you store and access the data later with minimal impact.
bog.py
This is where all the real magic happens. This could have been a class, but, since it's just a small, specialized project, I decided to write it using functions for kicks. Instead of self, I just use a bog variable. It's basically the same as if I wrote it like a class.
If this seems a bit hard to follow, please keep in mind it is only about 50 lines of code, if you don't count function definitions!
import random # Define bog size (5x5). bog_x = 5 bog_y = 5 # Make a new bog game board. It's a 2-d array (list) of characters. def make_bog(): bog = [[" " for x in range(bog_x)] for y in range(bog_y)] return bog # display the bog. Some may say this doesn't need to be a function, but it might be, especially if this was a class. def print_bog(bog): for row in bog: print(" ".join(row)) # fill the remaining empty spaces with letters. def fill_bog(bog): letters = 'qwertyuioplkjhgfdsazxcvbnm' for y in range(bog_y): for x in range(bog_x): # If it's an empty space replace it with a random letter. if bog[y][x] == ' ': bog[y][x] = random.choice(letters) # Try to put a word on the board a certain number of times. def try_put_bog(bog, word, n): while n > 0: if put_bog(bog, word): return True else: n = n - 1 return False # Try to put a word on the board in a random position. # This will take two passes. One to test and one to put. def put_bog(bog, word): # Choose a random spot and a random direction for the word. x = random.randint(0, bog_x - 1) y = random.randint(0, bog_y - 1) d = random.randint(1, 3) # Set dx and dy for 'walking' on the board in the specified direction if d == 1: # go up dx = 0 dy = -1 elif d == 2: # go to the right dx = 1 dy = 0 else: # go down dx = 0 dy = 1 # PASS 1: Test if we can add the word tx = x # test-x ty = y # test-y for i in range(len(word)): # Fail because outside if tx < 0 or ty < 0 or tx >= bog_x or ty >= bog_y: return False # Fail because there is already a letter. if bog[ty][tx] != ' ': return False tx = tx + dx #step to next letter position ty = ty + dy #step to next letter position # Fall-through pass 2: add the word. # For every letter, step through and add it. for c in word: bog[y][x] = c x = x + dx y = y + dy # Since we added the word, return true. return True
With this, the game is finished. It's a very simple concept.
Commentary
The big logic here is how the words are put on the board. Using dx and dy to walk accross a 2d landscape is a common theme. fill_bog() is the other interesting function (to a point), with the rest of the code being rather ordinary.
What most people are intened to remember from this example is how the list was filtered, “list = [x for x in list if try_put_bog(bog, x, 1000)]”, as well as the dx:dy stepping method. It also serves as a reminder for some basic programming concepts, like always put your variables at the start of a program, and don't necessarily need classes.
If your program plans to instantiate a class and then call it, and the class is only ever intended to be instantiated or created once, it is highly likely it does not need to be a class. If there is no overhead, then the use of a class is fine. But plesae do not use this archetype to promote callback-based programming. That just sucks,