Advertisement
Guest User

Untitled

a guest
Nov 23rd, 2017
156
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.97 KB | None | 0 0
  1. import random
  2. import re
  3. import time
  4. import string
  5. from copy import copy as duplicate
  6.  
  7.  
  8. class Crossword(object):
  9.  
  10. def __init__(self, cols, rows, empty='-', maxloops=2000, available_words=[]):
  11. self.cols = cols
  12. self.rows = rows
  13. self.empty = empty
  14. self.maxloops = maxloops
  15. self.available_words = available_words
  16. self.randomize_word_list()
  17. self.current_word_list = []
  18. self.debug = 0
  19. self.clear_grid()
  20.  
  21. def clear_grid(self): # initialize grid and fill with empty character
  22. self.grid = []
  23. for i in range(self.rows):
  24. ea_row = []
  25. for j in range(self.cols):
  26. ea_row.append(self.empty)
  27. self.grid.append(ea_row)
  28.  
  29. def randomize_word_list(self): # also resets words and sorts by length
  30. temp_list = []
  31. for word in self.available_words:
  32. if isinstance(word, Word):
  33. temp_list.append(Word(word.word, word.clue))
  34. else:
  35. temp_list.append(Word(word[0], word[1]))
  36. random.shuffle(temp_list) # randomize word list
  37. temp_list.sort(key=lambda i: len(i.word),
  38. reverse=True) # sort by length
  39. self.available_words = temp_list
  40.  
  41. def compute_crossword(self, time_permitted=1.00, spins=2):
  42. time_permitted = float(time_permitted)
  43.  
  44. count = 0
  45. copy = Crossword(self.cols, self.rows, self.empty,
  46. self.maxloops, self.available_words)
  47.  
  48. start_full = float(time.time())
  49. while (float(time.time()) - start_full) < time_permitted or count == 0: # only run for x seconds
  50. self.debug += 1
  51. copy.current_word_list = []
  52. copy.clear_grid()
  53. copy.randomize_word_list()
  54. x = 0
  55. while x < spins: # spins; 2 seems to be plenty
  56. for word in copy.available_words:
  57. if word not in copy.current_word_list:
  58. copy.fit_and_add(word)
  59. x += 1
  60. # print copy.solution()
  61. # print len(copy.current_word_list), len(self.current_word_list), self.debug
  62. # buffer the best crossword by comparing placed words
  63. if len(copy.current_word_list) > len(self.current_word_list):
  64. self.current_word_list = copy.current_word_list
  65. self.grid = copy.grid
  66. count += 1
  67. return
  68.  
  69. def suggest_coord(self, word):
  70. count = 0
  71. coordlist = []
  72. glc = -1
  73. for given_letter in word.word: # cycle through letters in word
  74. glc += 1
  75. rowc = 0
  76. for row in self.grid: # cycle through rows
  77. rowc += 1
  78. colc = 0
  79. for cell in row: # cycle through letters in rows
  80. colc += 1
  81. if given_letter == cell: # check match letter in word to letters in row
  82. try: # suggest vertical placement
  83. if rowc - glc > 0: # make sure we're not suggesting a starting point off the grid
  84. # make sure word doesn't go off of grid
  85. if ((rowc - glc) + word.length) <= self.rows:
  86. coordlist.append(
  87. [colc, rowc - glc, 1, colc + (rowc - glc), 0])
  88. except:
  89. pass
  90. try: # suggest horizontal placement
  91. if colc - glc > 0: # make sure we're not suggesting a starting point off the grid
  92. # make sure word doesn't go off of grid
  93. if ((colc - glc) + word.length) <= self.cols:
  94. coordlist.append(
  95. [colc - glc, rowc, 0, rowc + (colc - glc), 0])
  96. except:
  97. pass
  98. # example: coordlist[0] = [col, row, vertical, col + row, score]
  99. # print word.word
  100. # print coordlist
  101. new_coordlist = self.sort_coordlist(coordlist, word)
  102. # print new_coordlist
  103. return new_coordlist
  104.  
  105. # give each coordinate a score, then sort
  106. def sort_coordlist(self, coordlist, word):
  107. new_coordlist = []
  108. for coord in coordlist:
  109. col, row, vertical = coord[0], coord[1], coord[2]
  110. coord[4] = self.check_fit_score(
  111. col, row, vertical, word) # checking scores
  112. if coord[4]: # 0 scores are filtered
  113. new_coordlist.append(coord)
  114. random.shuffle(new_coordlist) # randomize coord list; why not?
  115. # put the best scores first
  116. new_coordlist.sort(key=lambda i: i[4], reverse=True)
  117. return new_coordlist
  118.  
  119. # doesn't really check fit except for the first word; otherwise just adds
  120. # if score is good
  121. def fit_and_add(self, word):
  122. fit = False
  123. count = 0
  124. coordlist = self.suggest_coord(word)
  125.  
  126. while not fit and count < self.maxloops:
  127.  
  128. if len(self.current_word_list) == 0: # this is the first word: the seed
  129. # top left seed of longest word yields best results (maybe
  130. # override)
  131. vertical, col, row = random.randrange(0, 2), 1, 1
  132. '''
  133. # optional center seed method, slower and less keyword placement
  134. if vertical:
  135. col = int(round((self.cols + 1)/2, 0))
  136. row = int(round((self.rows + 1)/2, 0)) - int(round((word.length + 1)/2, 0))
  137. else:
  138. col = int(round((self.cols + 1)/2, 0)) - int(round((word.length + 1)/2, 0))
  139. row = int(round((self.rows + 1)/2, 0))
  140. # completely random seed method
  141. col = random.randrange(1, self.cols + 1)
  142. row = random.randrange(1, self.rows + 1)
  143. '''
  144.  
  145. if self.check_fit_score(col, row, vertical, word):
  146. fit = True
  147. self.set_word(col, row, vertical, word, force=True)
  148. else: # a subsquent words have scores calculated
  149. try:
  150. col, row, vertical = coordlist[count][
  151. 0], coordlist[count][1], coordlist[count][2]
  152. except IndexError:
  153. return # no more cordinates, stop trying to fit
  154.  
  155. if coordlist[count][4]: # already filtered these out, but double check
  156. fit = True
  157. self.set_word(col, row, vertical, word, force=True)
  158.  
  159. count += 1
  160. return
  161.  
  162. def check_fit_score(self, col, row, vertical, word):
  163. '''
  164. And return score (0 signifies no fit). 1 means a fit, 2+ means a cross.
  165.  
  166. The more crosses the better.
  167. '''
  168. if col < 1 or row < 1:
  169. return 0
  170.  
  171. # give score a standard value of 1, will override with 0 if collisions
  172. # detected
  173. count, score = 1, 1
  174. for letter in word.word:
  175. try:
  176. active_cell = self.get_cell(col, row)
  177. except IndexError:
  178. return 0
  179.  
  180. if active_cell == self.empty or active_cell == letter:
  181. pass
  182. else:
  183. return 0
  184.  
  185. if active_cell == letter:
  186. score += 1
  187.  
  188. if vertical:
  189. # check surroundings
  190. if active_cell != letter: # don't check surroundings if cross point
  191. # check right cell
  192. if not self.check_if_cell_clear(col + 1, row):
  193. return 0
  194.  
  195. # check left cell
  196. if not self.check_if_cell_clear(col - 1, row):
  197. return 0
  198.  
  199. if count == 1: # check top cell only on first letter
  200. if not self.check_if_cell_clear(col, row - 1):
  201. return 0
  202.  
  203. if count == len(word.word): # check bottom cell only on last letter
  204. if not self.check_if_cell_clear(col, row + 1):
  205. return 0
  206. else: # else horizontal
  207. # check surroundings
  208. if active_cell != letter: # don't check surroundings if cross point
  209. # check top cell
  210. if not self.check_if_cell_clear(col, row - 1):
  211. return 0
  212.  
  213. # check bottom cell
  214. if not self.check_if_cell_clear(col, row + 1):
  215. return 0
  216.  
  217. if count == 1: # check left cell only on first letter
  218. if not self.check_if_cell_clear(col - 1, row):
  219. return 0
  220.  
  221. if count == len(word.word): # check right cell only on last letter
  222. if not self.check_if_cell_clear(col + 1, row):
  223. return 0
  224.  
  225. if vertical: # progress to next letter and position
  226. row += 1
  227. else: # else horizontal
  228. col += 1
  229.  
  230. count += 1
  231.  
  232. return score
  233.  
  234. def set_word(self, col, row, vertical, word, force=False): # also adds word to word list
  235. if force:
  236. word.col = col
  237. word.row = row
  238. word.vertical = vertical
  239. self.current_word_list.append(word)
  240.  
  241. for letter in word.word:
  242. self.set_cell(col, row, letter)
  243. if vertical:
  244. row += 1
  245. else:
  246. col += 1
  247. return
  248.  
  249. def set_cell(self, col, row, value):
  250. self.grid[row - 1][col - 1] = value
  251.  
  252. def get_cell(self, col, row):
  253. return self.grid[row - 1][col - 1]
  254.  
  255. def check_if_cell_clear(self, col, row):
  256. try:
  257. cell = self.get_cell(col, row)
  258. if cell == self.empty:
  259. return True
  260. except IndexError:
  261. pass
  262. return False
  263.  
  264. def solution(self): # return solution grid
  265. outStr = ""
  266. for r in range(self.rows):
  267. for c in self.grid[r]:
  268. outStr += '%s ' % c
  269. outStr += '\n'
  270. return outStr
  271.  
  272. def word_find(self): # return solution grid
  273. outStr = ""
  274. for r in range(self.rows):
  275. for c in self.grid[r]:
  276. if c == self.empty:
  277. outStr += '%s ' % string.ascii_lowercase[
  278. random.randint(0, len(string.ascii_lowercase) - 1)]
  279. else:
  280. outStr += '%s ' % c
  281. outStr += '\n'
  282. return outStr
  283.  
  284. def order_number_words(self): # orders words and applies numbering system to them
  285. self.current_word_list.sort(key=lambda i: (i.col + i.row))
  286. count, icount = 1, 1
  287. for word in self.current_word_list:
  288. word.number = count
  289. if icount < len(self.current_word_list):
  290. if word.col == self.current_word_list[icount].col and word.row == self.current_word_list[icount].row:
  291. pass
  292. else:
  293. count += 1
  294. icount += 1
  295.  
  296. # return (and order/number wordlist) the grid minus the words adding the
  297. # numbers
  298. def display(self, order=True):
  299. outStr = ""
  300. if order:
  301. self.order_number_words()
  302.  
  303. copy = self
  304.  
  305. for word in self.current_word_list:
  306. copy.set_cell(word.col, word.row, word.number)
  307.  
  308. for r in range(copy.rows):
  309. for c in copy.grid[r]:
  310. outStr += '%s ' % c
  311. outStr += '\n'
  312.  
  313. outStr = re.sub(r'[a-z]', ' ', outStr)
  314. return outStr
  315.  
  316. def word_bank(self):
  317. outStr = ''
  318. temp_list = duplicate(self.current_word_list)
  319. random.shuffle(temp_list) # randomize word list
  320. for word in temp_list:
  321. outStr += '%s\n' % word.word
  322. return outStr
  323.  
  324. def legend(self): # must order first
  325. outStr = ''
  326. for word in self.current_word_list:
  327. outStr += '%d. (%d,%d) %s: %s\n' % (word.number,
  328. word.col, word.row, word.down_across(), word.clue)
  329. return outStr
  330.  
  331.  
  332. class Word(object):
  333.  
  334. def __init__(self, word=None, clue=None):
  335. self.word = re.sub(r'\s', '', word.lower())
  336. self.clue = clue
  337. self.length = len(self.word)
  338. # the below are set when placed on board
  339. self.row = None
  340. self.col = None
  341. self.vertical = None
  342. self.number = None
  343.  
  344. def down_across(self): # return down or across
  345. if self.vertical:
  346. return 'down'
  347. else:
  348. return 'across'
  349.  
  350. def __repr__(self):
  351. return self.word
  352.  
  353. # end class, start execution
  354.  
  355. #start_full = float(time.time())
  356.  
  357. word_list = ['ТаШкЕнТ', 'The dried, orange yellow plant used to as dye and as a cooking spice.'], \
  358. ['солнце', 'Dark, sour bread made from coarse ground rye.'], \
  359. ['вашингтон', 'An agent, such as yeast, that cause batter or dough to rise..'], \
  360. ['рыба', 'Musical conclusion of a movement or composition.'], \
  361. ['раствор', 'A heroic champion or paragon of chivalry.'], \
  362. ['первый', 'Shifting the emphasis of a beat to the normally weak beat.'], \
  363. ['муха', 'A large bird of the ocean having a hooked beek and long, narrow wings.'], \
  364. ['класс', 'Musical instrument with 46 or more open strings played by plucking.'], \
  365. ['салон', 'A solid cylinder or disk that fits snugly in a larger cylinder and moves under pressure as in an engine.'], \
  366. ['верблюд', 'A smooth chery candy made from suger, butter, cream or milk with flavoring.'], \
  367. ['корал', 'A rock-like deposit of organism skeletons that make up reefs.'], \
  368. ['камбала', 'The time of each morning at which daylight begins.'], \
  369. ['кот', 'A resin derived from the sap of various pine trees.'], \
  370. ['кулек', 'A long, narrow, deep inlet of the sea between steep slopes.'], \
  371. ['клад', 'Either of two fleshy folds surrounding the mouth.'], \
  372. ['яблоко', 'The egg-shaped citrus fruit having a green coloring and acidic juice.'], \
  373. ['крым', 'A mass of fine water droplets in the air near or in contact with the ground.'], \
  374. ['чума', 'A widespread affliction or calamity.'], \
  375. ['муха', 'A strand of twisted threads or a long elaborate narrative.'], \
  376. ['бум', 'A snide, slightly stifled laugh.']
  377.  
  378. a = Crossword(13, 13, '-', 5000, word_list)
  379. a.compute_crossword(2)
  380. print(a.word_bank())
  381. print(a.solution())
  382. print(a.word_find())
  383. print(a.display())
  384. print(a.legend())
  385. print(len(a.current_word_list), 'out of', len(word_list))
  386. print(a.debug)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement