Recently I saw a lot of people playing idioms crossword game, so first use PyGame to make a bar, it took a long time to finally complete, attached to the effect of the picture.There are two main py files

The encapsulated operation class idiom_lib.py

# -*- coding=utf-8 -*-
import sys
import random

if sys.version_info < (3.0):
    reload(sys)
    sys.setdefaultencoding('utf-8')
elif sys.version_info <= (3.3) :import imp
    imp.reload(sys)
else:
    import importlib
    importlib.reload(sys)

Class used to store a verse
class IdiomInfo(object) :
    def __init__(self,idiom) :
        self.idiom = idiom # Verse text
        self.dire = 0 # Verse direction 0 means horizontal and 1 means vertical
        self.word_arr = [] # list of WordInfo objects for each kanji in the verse

    def to_str(self) :
        arr = []
        for word_info in self.word_arr:
            arr.append('%s %s %s'%(word_info.i,word_info.j,word_info.word))
        return '%s,%s,%s'%(self.idiom, self.dire, '|'.join(arr))

Class used to hold a single text
class WordInfo(object) :
    def __init__(self, word, i, j) :
        self.i = i # the x coordinate of the text
        self.j = j # the y coordinate of the text
        self.word = word # Text content
        self.is_lock = True # Whether the text needs to be filled in
        self.state = -1
        self.hide_index = -1
        self.op_hide_index = -1

# Corresponding to the middle area of the number of grid construction matrix class, easy to manage the text in the middle
class Matrix(object) :
    rows = 0 # lines
    cols = 0 # the number of columns
    data = [] # All text

    def __init__(self, rows, cols, data=None) :
        self.rows = rows
        self.cols = cols
        if data is None: data = [None for i in range(rows * cols)]
        self.data = data

    # set text
    def set_val(self, x, y, val) :
        self.data[y * self.cols + x] = val

    # fetch text
    def get_val(self, x, y) :
        return self.data[y * self.cols + x]

    def exist_val_four_around(self, x, y, ignore_set) :
        Param x: column number :param Y: line number :param ignore_set: grid to ignore :return: ""
        move_arr = [(-1.0), (1.0), (0, -1), (0.1)] # Left, right, up, down, four directions

        for dx,dy in move_arr:
            tx = x + dx
            ty = y + dy
            if (tx,ty) in ignore_set: continue
            if tx < 0 or tx >= self.cols or ty <0 or ty >= self.rows: continue
            if self.data[ty * self.cols + tx]: return True
        return False

# Verse manipulation library
class IdiomLib() :
    def __init__(self, block_num=12) :
        self.word_dic={} # What verse does a word have, the format is: {word :[verse, verse],... }
        self.word_arr=[] # List of all text
        self.block_num=block_num # Number of horizontal and vertical grids
        self.matrix = Matrix(self.block_num, self.block_num) # Text matrix for the middle region
        self.idiom_dic={} # List of verses for the current level in the format: {verse: verse object IdiomInfo}
        self.all_word_num=0 # The amount of text in the current level
        self.hide_arr = [] [[x,y, text,None],...]

    Load the verse from the file
    def load_idiom_from_file(self, filename='words.txt') :
        if sys.version_info < (3.0): 
            f = open(filename)
        else:
            f = open(filename,encoding='UTF-8')
        all_idiom = f.readlines()
        f.close()

        for idiom in all_idiom:
            if sys.version_info < (3.0):
                idiom = idiom.strip().decode('utf-8')
            else:
                idiom = idiom.strip()
            for word in idiom:
                if word not in self.word_dic: 
                    self.word_dic[word] = [idiom]
                else:
                    self.word_dic[word].append(idiom)

        self.word_arr = list(self.word_dic.keys())

    def check_new_idiom(self, new_idiom, new_dire, word_info) :
        "" Check if the new new verse is valid, invalid scene 1. Text is found beyond boundaries after typesetting 2. Syntax :param new_idiom: Param new_info: current text object :return: ""
        windex = new_idiom.index(word_info.word)
        cx,cy = word_info.i, word_info.j
        ignore_set = set([(cx,cy)])

        new_idiom_word_arr=[]
        for i in range(-windex,-windex+len(new_idiom)): 
            if i==0: 
                new_idiom_word_arr.append(word_info)
            else:
                tx = cx+i  if new_dire == 0 else  cx
                # Laterally out of bounds
                if tx < 0 or tx >= self.block_num: return None.None

                ty = cy if new_dire == 0 else cy+i
                # Out of bounds vertically
                if ty < 0 or ty >= self.block_num: return None.None

                Is there any text around every word except the first
                if self.matrix.exist_val_four_around(tx, ty, ignore_set): return None.None

                old_word_info = self.matrix.get_val(tx, ty)
                if old_word_info:
                    return None.None

                new_word_info = WordInfo(new_idiom[i+windex], tx, ty)
                new_idiom_word_arr.append(new_word_info)

        return new_idiom_word_arr,windex

    def add_idiom_to_matrix(self, idiom_num) :
        if idiom_num == 0: return 0
        for idiom,idiom_info in self.idiom_dic.items(): Walk through the selected verse
            dire = idiom_info.dire
            new_dire = 1 - dire # Verses alternate horizontal and vertical because horizontal =0, vertical =1,1- horizontal = vertical,1- vertical = horizontal
            for word_info in idiom_info.word_arr: Walk over every word in the selected verse
                word = word_info.word
                idiom_list = self.word_dic[word]
                for new_idiom in idiom_list: # Walk through the verse of the word
                    if new_idiom in self.idiom_dic: continue # If verse is already selected, skip
                    new_idiom_word_arr,windex = self.check_new_idiom(new_idiom, new_dire, word_info) # Check verse validity
                    if new_idiom_word_arr:
                        new_idiom_info = IdiomInfo(new_idiom)
                        new_idiom_info.dire = new_dire
                        # Place each word of the verse in the text matrix
                        for new_index in range(len(new_idiom_word_arr)):
                            new_word_info = new_idiom_word_arr[new_index]
                            if new_index == windex:
                                new_idiom_info.word_arr.append(word_info)
                            else:
                                self.matrix.set_val(new_word_info.i, new_word_info.j , new_word_info)
                                new_idiom_info.word_arr.append(new_word_info)
                        self.idiom_dic[new_idiom] = new_idiom_info

                        # Continue to add the next verse
                        return len(new_idiom) -1 + self.add_idiom_to_matrix(idiom_num - 1)

        return 0

    def get_idiom_matrix(self, idiom_num) :
        self.idiom_dic={}
        cx = int(self.block_num/2) -1
        cy = int(self.block_num/2) -1

        # Pick a random word
        n = random.randint(0.len(self.word_arr)-1)
        word = self.word_arr[n]
        # Take the first verse in the list of verses made up of this word
        idiom = self.word_dic[word][0]
        wn = len(idiom)

        # Save the first verse in the dictionary
        self.idiom_dic[idiom] = IdiomInfo(idiom)

        # Place each word of the verse in the text matrix
        for i in range(len(idiom)):
            word_info = WordInfo(idiom[i],cx-int(wn/2) +1+i,cy)
            self.matrix.set_val(cx-int(wn/2) +1+i,cy,word_info)
            self.idiom_dic[idiom].word_arr.append(word_info)

        # Add the next verse
        wn += self.add_idiom_to_matrix(idiom_num-1)
        return wn

    def get_hide_arr(self, percent) :
        self.hide_arr=[] [[x,y, text,None],...]
        idiom_word_arr = [] # list type: [[verse,[text object, text object...]],...]

        for k,v in self.idiom_dic.items():
            arr = []
            for word_info in v.word_arr:
                arr.append(word_info)
            idiom_word_arr.append([k, arr])
        # Sort the verse from most to least number of words
        idiom_word_arr.sort(key=lambda x:-len(x[-1]))

        idiom_index = 0
        while len(self.hide_arr) < self.all_word_num*percent:
            tmp_arr = idiom_word_arr[idiom_index%len(idiom_word_arr)][1] # Get a set of words for a verse
            n = random.randint(0.len(tmp_arr)-1) # Select a random position in a set of text
            info = tmp_arr.pop(n) # remove text
            word=info.word
            info.word = ' ' # Empty the text on the grid
            info.hide_index = len(self.hide_arr) # Record the location index in the text hidden list
            info.is_lock = False Text on the grid is clickable
            self.hide_arr.append([info.i,info.j,word,None]) Add text to the hidden list
            idiom_index+=1 # Go to the next verse

        return self.hide_arr  

    def get_next_select(self, x, y) :
        Select the next selected grid based on the specified location :param x: :param y: :return:"
        arr = []
        for i in range(self.block_num):
            for j in range(self.block_num):
                info = self.matrix.get_val(i, j)
                if info is not None and len(info.word) == 0:
                    dist = (i-x)*(i-x)+(j-y)*(j-y)
                    if i<x: dist+=0.2 # the grid is to the left, increasing the distance by 0.2, and sorting will be further back
                    if j<y: dist+=0.4 Increase the distance by 0.4 to sort behind
                    arr.append((i,j,dist))
        if len(arr) == 0:
            return None
        Sort by the distance of all optional cells
        arr.sort(key=lambda x:x[-1])
        return (arr[0] [0],arr[0] [1])

    def check_idiom(self) :
        for idiom, idiom_info in self.idiom_dic.items():
            tmp_idiom_str = ' '
            word_arr = idiom_info.word_arr
            for word_info in word_arr:
                word = word_info.word
                if len(word) > 0:
                    tmp_idiom_str+=word
            if len(tmp_idiom_str) == len(idiom):
                state = 1 if tmp_idiom_str == idiom else 2
            else:
                state = 0

            for word_info in word_arr:
                ifword_info.state ! =1: word_info.state = state

        for idiom, idiom_info in self.idiom_dic.items():
            word_arr = idiom_info.word_arr
            for word_info in word_arr:
                ifword_info.state ! =1:
                    return False
        return True

    stage = 1

    def init(self, new_stage) :
        idiom_num = int(new_stage/5) +3
        if new_stage>100:
            percent = 0.7 The percentage of hidden text does not change after level # 100
        else:
            percent = 0.2+(new_stage*1.0/100) * (0.7-0.2)
        self.matrix = Matrix(self.block_num, self.block_num)
        # Generate a set of verses
        self.all_word_num = self.get_idiom_matrix(idiom_num)
        Extract some hidden words from the poem in proportion
        self.get_hide_arr(percent)
        The first missing word in the middle region is selected by default
        self.select_rect = self.hide_arr[0] [0],self.hide_arr[0] [1]

if __name__ == '__main__':
	pass
    # lib = IdiomLib(block_num=10)
    # lib.load_idiom_from_file()

    # arr = []
    # for i in range(1,101):
    # lib.init(i)
    # idiom_arr = []
    # for k,v in lib.idiom_dic.items():
    # idiom_arr.append(v.to_str())
    # hide_arr = []
    # for x,y,word,op in lib.hide_arr:
    # hide_arr.append('%s %s %s'%(x,y,word))
    # arr.append({'hide_num':len(hide_arr),'block_num':lib.block_num, 'word_num':lib.all_word_num,'idiom_arr':'; '.join(idiom_arr),'hide_arr':'; '.join(hide_arr)})
    # #arr.sort(cmp=lambda x,y:cmp(x['hide_num']*2+x['word_num'], y['hide_num']*2+y['word_num']))
    # arr.sort(key=lambda x:x['hide_num']*2+x['word_num'])

    # import json
    # f = open('idiom.json','w+') 
    # f.write(json.dumps(arr))
    # f.close()
Copy the code

The main program main. Py

# -*- coding=utf-8 -*-
import sys
import random
import pygame
from pygame.locals import *
from idiom_lib import IdiomLib

if sys.version_info < (3.0):
    reload(sys)
    sys.setdefaultencoding('utf-8')
elif sys.version_info <= (3.3) :import imp
    imp.reload(sys)
else:
    import importlib
    importlib.reload(sys)

# Number of horizontal and vertical characters
block_num=12
lib = IdiomLib(block_num=block_num)
lib.load_idiom_from_file()

# pixels vacated at the top (total height vacated at the top is header_height+main_space)
header_height = 30
# Empty pixels around the middle region
main_space = 20

# Height and width of each text in pixels
block_size = 36
# How many pixels to indent per grid
bspace = 2
space = 20
# Total width = Text pixels * Number of text + four peripherals *2
width = block_size * block_num + main_space * 2
# Total height = Top height + Text pixel * Number of text + four peripherals *2+ bottom height to be selected (i.e. : text pixel *3+ white space on both sides to be selected *3)
height = header_height + block_size * block_num + main_space * 2 + block_size * 3 + space * 3

pygame.init()
screen = pygame.display.set_mode((width,height))
screencaption = pygame.display.set_caption(U 'Fill in the blanks with poetry')

font = pygame.font.Font(u'syht.otf'.int(block_size*0.8))

dray_blue = 50.50.200
white = 255.255.255
#textImage = font. Render (u' hello ', True, white)

Load and stretch the background image
bg_image = pygame.image.load('bg.jpeg')
bg_image = pygame.transform.scale(bg_image,(width, height))

# Load and stretch the middle area of the image
bg2_image = pygame.image.load('bg2.jpeg')
bg2_image = pygame.transform.scale(bg2_image,(block_size*block_num,block_size*block_num))

Load and stretch the Megabyte background image for each Chinese character
block_bg_image = pygame.image.load('tzg.jpg')
block_bg_image = pygame.transform.scale(block_bg_image,(block_size-bspace*2,block_size-bspace*2))


stage = 1
lib.init(stage) Initialize the first level

Get the height of the text width to keep the text centered
stage_textImage = pygame.font.Font(u'syht.otf'.30).render(U '%s'%stage, True, dray_blue)
stage_font_width, stage_font_height = stage_textImage.get_size()
stage_x = int((width - stage_font_width)/2)
stage_y = int((header_height - stage_font_height)/2) +int(main_space/2)

while True:
	for event in pygame.event.get():
		if event.type == pygame.QUIT:
		   	pygame.quit()
		   	exit()

		if event.type == MOUSEBUTTONDOWN:
			pressed_array = pygame.mouse.get_pressed()
			if pressed_array[0]:
				x, y = pygame.mouse.get_pos()

				Calculates the selected grid index
				xi = (x - main_space) // block_size
				yi = (y - header_height - main_space) // block_size
				if xi>=0 and xi<block_num and yi>=0 and yi<block_num:
					Extract the object of the selected grid
					info = lib.matrix.get_val(xi, yi)
					Info. State ==1 is not allowed to click back. Info. Hide_index >=0 is not allowed to click back
					if info andinfo.state ! =1 and info.hide_index >= 0:
						if info.op_hide_index >= 0:
							lib.hide_arr[info.op_hide_index][-1] = None
							info.word = ' '
							info.op_hide_index = -1
							lib.check_idiom()
						lib.select_rect = xi, yi

				sx = main_space
				sy = header_height + main_space+ block_size*block_num +space
				n = 0 # The text number of the selection area below
				# This loop is used to determine if the mouse is hovering over the text in the selection area below
				for hi in range(len(lib.hide_arr)): Walk through the list of hidden words
					tmp_x = sx + (n%block_num)*block_size # Start x pixel position of the NTH word
					tmp_y = sy + int(n/block_num)*block_size # Position of the initial pixel in the y direction of the NTH word
					if lib.hide_arr[hi][-1] is None and x >= tmp_x and x <= tmp_x+block_size-bspace*2 and y >= tmp_y and y<= tmp_y+block_size-bspace*2:
						# Remove the object from the selected fillin
						info = lib.matrix.get_val(lib.select_rect[0],lib.select_rect[1])
						# Assign to the object
						info.word = lib.hide_arr[hi][2]
						info.op_hide_index = hi Record the location of the source to be selected to exit the text
						info.state = 0 The state is not completed
						lib.hide_arr[hi][-1] = lib.select_rect
						# Select the next recommended fill location
						lib.select_rect = lib.get_next_select(lib.select_rect[0],lib.select_rect[1])
						flag = lib.check_idiom()
						if flag:
							stage += 1
							lib.init(stage)
							stage_textImage = pygame.font.Font(u'syht.otf'.30).render(U '%s'%stage, True, dray_blue)
						break
					n += 1

	screen.blit(bg_image, (0.0))
	screen.blit(stage_textImage, (stage_x,stage_y))

	# Panel corresponds to the artboard in the middle area
	panel = screen.subsurface((main_space,header_height+main_space,block_size*block_num,block_size*block_num))
	panel.blit(bg2_image, (0.0))

	# Draw the text in the middle position
	for i in range(block_num):
		for j in range(block_num):
			info = lib.matrix.get_val(i,j)
			if info is not None:
				bx = block_size*i+bspace
				by = block_size*j+bspace
				panel.blit(block_bg_image, (bx,by))
				
				if info.state == 1:
					textImage = font.render(info.word, True, (30.144.30)) # Word green on verse correctly completed
				elif info.is_lock == 1:
					textImage = font.render(info.word, True, (100.100.100)) # Do not click on gray
				elif info.state == 2:
					textImage = font.render(info.word, True, (255.0.0)) # error red
				else:
					textImage = font.render(info.word, True, dray_blue) # default blue

				tw, th = textImage.get_size()
				dx=int((block_size-bspace*2-tw)/2)
				dy=int((block_size-bspace*2-th)/2)
				panel.blit(textImage, (bx+dx,by+dy))
				if (i,j) == lib.select_rect: It is recommended to fill the position with a red border
					pygame.draw.rect(panel,(255.0.0),(bx,by,block_size-bspace*2,block_size-bspace*2),2)

	# Draw the text in the selected text area below
	sx = main_space
	sy = header_height + main_space+ block_size*block_num +space
	n = 0
	for i,j,word,op in lib.hide_arr:
		screen.blit(block_bg_image, (sx + (n%block_num)*block_size,sy + int(n/block_num)*block_size))
		if op is None:
			textImage = font.render(word, True, dray_blue)
			tw, th = textImage.get_size()
			dx=int((block_size-bspace*2-tw)/2)
			dy=int((block_size-bspace*2-th)/2)
			screen.blit(textImage, (dx+sx+ (n%block_num)*block_size,dy+sy+ int(n/block_num)*block_size))
		n+=1

	pygame.display.update()
Copy the code

That’s it, but there are a few extra dependencies: Bg.jpeg for the background of the entire interface bG2.jpeg for the background of the top half tzg.jpg For the background of each text grid words.txt a list file of idioms (one idiom per line), Syht. otf a font library for normal display of Chinese

You can adjust these two parameters if you are too busy or too small

block_size = 32
block_num=12
Copy the code

Block_size specifies the size of the grid. How many grids can be displayed vertically or horizontally in the upper part of block_num

Block_size = 26, block_num=18Block_size = 40, block_num=10

The complete code and resources have been uploaded: github: github.com/zhangenter/…