Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Crossword/Codeword grid query
-
Hi, I'm not used to coding with Python so using Pythonista to help learn the ways on my mobile device for convenience. I'm trying to rewrite a codeword solver I wrote in Visual Basic for Excel. I used the spreadsheet grid for the user interface but not sure of the best method to adopt in Pythonista. The idea is to draw a crossword type grid (codeword) which can be edited and updated for each time a letter Is substituted in the grid.
Not sure whether to use layers, user interface or separate tiles etc.Still learning so any pointers or examples for crossword type interface coding help would be greatly appreciated.
Basically my program allows the user to see the effect of a particular letter when it is substituted in the grid. Thus helping to see if other areas of the grid look feasible. If not happy the then the operation can be corrected. Much quicker than pencil and paper apart from the initial set up of the grid.
Thanks for any help.
Regards
Gary -
Are your grids a fixed size? Or dynamic?
Ui module is somewhat easier to use IMHO than scene. You could create an array of TableViews for each column. Or, you could draw an array of ui.TextFields for example.
-
I also think ui is the better choice. First you draw an black/white image and then make a matrix out of it. With this 0/1 matrix you draw a button matrix. Each button opens a popup menu where you can change the letter or word (button.name). Maybe you want to search for the pixel editor.
-
Thanks for these chaps.
JonB my grids usually 13 or 15 cells depending on which newspaper/source for the puzzle. I suppose the cells could remain the same size as long as grid can be viewed clearly.Brumm, I think I follow. A black or white background for the buttons running a matrix grid?
I'll have a study of the UI module. Can a UI be pinched/zoomed? Hope to view on iPhone as well as iPad you see.
Thanks again
Gary -
I would recommend the ui.ScrollView for this.
-
Rather than a black or white image, you might consider just using
bg_color
to color the tiles. You might consider the action to be a function that takes sender, row, and col, but then you use functools partial to give it the proper one argument, so that you can easily have access to row and column info.You could also do this with a grid of TextFields, if you want to be able to directly enter into the grid. Textfield delegate methods would be used to call your grid update functions, and limit each textfield to 1character.
-
Something to throw darts at... I did not find .bg_color to be the answer.
import console, ui crossword_rows = 15 crossword_cols = 22 gap = 4 class CrosswordView(ui.View): def __init__(self): self.present() min_dimension = min(self.width / crossword_cols, self.height / crossword_rows) #self.squares = [] for i in xrange(crossword_cols): x = i * min_dimension for j in xrange(crossword_rows): y = j * min_dimension square = ui.TextField(frame = (x, y, min_dimension-gap, min_dimension-gap)) square.alignment = ui.ALIGN_CENTER square.bg_color = 'black' # 'white' # 'grey' if (i+j) % 2 else 'white' square.delegate = self # self.textfield_did_change() will be called square.name = '{} {}'.format(i,j) square.text = 'X' self.add_subview(square) #self.squares.append(square) def textfield_did_change(self, textfield): textfield.text = textfield.text[:1] # no longer than 1 character x, y = textfield.name.split() fmt = 'x={}, y={}, text={}' console.hud_alert(fmt.format(x, y, textfield.text)) CrosswordView()
-
One step further by adding a
crossword_entry
which lets you specify row, column, direction, and length of a word...import collections, console, ui crossword_rows = 15 crossword_cols = 22 gap = 4 crossword_entry = collections.namedtuple('crossword_entry', 'row col direction length') puzzle_entries = ( crossword_entry( 0, 0,'A',5), crossword_entry( 5, 5,'D',7), crossword_entry( 8, 3,'a',5), crossword_entry(10,20,'d',4) ) def make_matrix(row_count, col_count, puzzle_entries): the_matrix = [[' ' for col in xrange(col_count)] for row in xrange(row_count)] for the_entry in puzzle_entries: if the_entry.direction.upper() == 'A': # across for i in xrange(the_entry.length): the_matrix[the_entry.row][the_entry.col+i] = 'X' else: # down for i in xrange(the_entry.length): the_matrix[the_entry.row+i][the_entry.col] = 'X' return the_matrix class CrosswordView(ui.View): def __init__(self, puzzle_matrix): row_count = len(puzzle_matrix) col_count = len(puzzle_matrix[0]) self.present() min_dimension = min(self.width / col_count, self.height / row_count) for row in xrange(row_count): y = row * min_dimension for col in xrange(col_count): if not puzzle_matrix[row][col].strip(): continue x = col * min_dimension square = ui.TextField(frame = (x, y, min_dimension-gap, min_dimension-gap)) square.alignment = ui.ALIGN_CENTER square.bg_color = 'blue' if (row+col) % 2 else 'white' square.delegate = self # self.textfield_did_change() will be called square.text = 'X' square.name = '{} {}'.format(row, col) self.add_subview(square) def textfield_did_change(self, textfield): textfield.text = textfield.text[:1] # no longer than 1 character row, col = textfield.name.split() fmt = 'row={}, col={}, text={}' console.hud_alert(fmt.format(row, col, textfield.text)) if __name__ == '__main__': the_matrix = make_matrix(crossword_rows, crossword_cols, puzzle_entries) #for row in the_matrix: # print_matrix() # print(''.join(col for col in row)) CrosswordView(the_matrix)
-
Thanks for this. I'll have a play 😄
-
I've been having a play around and trying to populate a matrix with buttons but can't find much in the docs to help. I have got a button to swap from black to white at the touch of the screen but not sure how to populate with more than one button. When I try 2 they end up on top of each other. Dynamic matrix preferred so not using the builder. Can't see anything for the button class attributes relating to position/size or a constructor. Is there a view required for each object or view? Please can someone explain how it is done?
-
The frame property lets you set size and position in one go.
Or, you can set width, height, x and y separately.
Frame can be set in the constructor, as can the background color, many other parameters can not.You basically have to do the math yourself to place on a grid. See ccc's example above, where he computes x and y for each column/row, and initializer the button using those values. His code seems like it would be a good jumping off point, and has a good interface for encoding the row,col inside the name.
-
Thanks for the reply. I've managed this so far but not sure how to manage the scope for my Constant 'status'.
Once the 'Done' button is tapped 'status' should be set to 2 and then stop the black/white toggle code using the 'if' block. I think scope is the problem here but not sure how to cure it. Any help appreciated.
Also don't know how to show my code like others do on the message sorry.import console, ui, time
crossword_rows = 4
crossword_cols = 4
gap = 1
status = 1def button_tapped1(square):
#print 'status = '+ str(status)
tup = tuple([1.0,1.0,1.0,1.0]) # white values
if status==1:
#print 'status = '+ str(status)
if str(tup) == str(square.bg_color):
square.bg_color = 'black'
else:
square.bg_color = 'white'
elif status==2:
#button_tapped2()
passdef button_tapped2(button):
#add square number
#print 'status = '+ str(status)
status = 2
button.title = 'ok'
#print 'status = '+ str(status)
passclass CrosswordView(ui.View):
def init(self):
self.present(hide_title_bar=False )
self.background_color = (0, 1.0, 2.0, 0.4)
min_dimension = min(self.width / crossword_cols, self.height / crossword_rows)
#self.squares = []
for i in xrange(crossword_cols):
x = i * min_dimension
for j in xrange(crossword_rows):
time.sleep(.05)
y = j * min_dimension
square = ui.Button(frame = (x, y, min_dimension-gap, min_dimension-gap))
#square.alignment = #ui.ALIGN_CENTER
square.bg_color = 'white'
#square.title = 'X'
self.add_subview(square)
square.action = button_tapped1button = ui.Button(frame = (x-150, y+80, min_dimension*2, min_dimension*2)) self.add_subview(button) button.title = 'Done' button.background_color = 'white' button.action = button_tapped2 #print 'another visit'
CrosswordView()
print 'status = '+ str(status) -
From help('SCOPING')
If a name is bound in a block, it is a local variable of that block.
meaning, since you are trying to modify status, it is a local to
button_tapped2
. (I learned something today!)Declare
status
as global in that function.As an aside, for posting code to the forum, if works best if you insert a blank line, then a line containing three backticks
```
, then your code, then the three backticks again.
On ios, backtick is found by long tapping the single quote, and selecting the leftmost item.
Or, go into settings app-> keyboard -> shortcuts, and add a shortcut.
I map ,,, to ```` `, allowing quick entry without having to go to the symbol page of the ipad keyboard``` Like this ```
-
Putting the word
python
directly after the first three backticks will give you syntax highlighting for Python code.@GaryGadget, it would be cool if you could create a GitHub repo of your code so that we could collaborate on it.
-
JonB,
I had been trying Global but it wasn't working but since you confirmed my thoughts I succeed thanks. I didn't realise it had to be declared global in each block!Here's my attempt so far as test code for setting up the matrix black/white and to include it in this message properly.
Thanks for your help guys.
import console, ui, time crossword_rows = 4 crossword_cols = 4 gap = 1 status = 1 def button_tapped1(square): global status print 'status = '+ str(status) tup = tuple([1.0,1.0,1.0,1.0]) # white values if status==1: #print 'status = '+ str(status) if str(tup) == str(square.bg_color): square.bg_color = 'black' else: square.bg_color = 'white' elif status==2: #button_tapped2() pass def button_tapped2(button): #add square number global status print 'status = '+ str(status) status = 2 button.title = 'Colours set.' #print 'status = '+ str(status) #pass class CrosswordView(ui.View): def __init__(self): self.present(hide_title_bar=False ) self.background_color = (0, 1.0, 2.0, 0.4) min_dimension = min(self.width / crossword_cols, self.height / crossword_rows) #self.squares = [] for i in xrange(crossword_cols): x = i * min_dimension for j in xrange(crossword_rows): time.sleep(.05) y = j * min_dimension square = ui.Button(frame = (x, y, min_dimension-gap, min_dimension-gap)) #square.alignment = #ui.ALIGN_CENTER square.bg_color = 'white' #square.title = 'X' self.add_subview(square) square.action = button_tapped1 button = ui.Button(frame = (x-150, y+80, min_dimension*2, min_dimension*2)) self.add_subview(button) button.title = 'Done' button.background_color = 'white' button.action = button_tapped2 #print 'another visit' CrosswordView() #print 'status = '+ str(status)
-
Maybe this code help you:
import ui class MakeButtonArray(object): def __init__(self): self.view = ui.View() self.view.name = 'Buttons' self.view.background_color = 'grey' self.view.present('fullscreen') self.edit = ui.TextField() self.edit.text = '' self.edit.center = (50,50) self.edit.width = self.view.width self.edit.height = 30 self.edit.border_color = 'black' self.edit.border_width = 1 self.edit.placeholder = 'single letter or whole word' self.edit.enabled = False self.edit.delegate = self self.edit.clear_button_mode = 'while_editing' self.view.add_subview(self.edit) self.button_array = [] self.word1 = 'TELEFON' for i in range(len(self.word1)): self.button_array.append(ui.Button(title=self.word1[i])) x = 100 y = 100 + (i * 50) self.button_array[i].x = x self.button_array[i].y = y self.button_array[i].width = 50 self.button_array[i].height = 50 self.button_array[i].border_color = 'black' self.button_array[i].border_width = 1 self.button_array[i].name = str(i) self.button_array[i].bg_color = 'white' self.button_array[i].action = self.button_array_action self.view.add_subview(self.button_array[i]) i += 1 def button_array_action(self, sender): self.edit.enabled = True self.edit.text = self.word1 #self.edit.text = 'sender.name:' + sender.name + ' // "' + sender.title + '" // ' + 'sender.x:' + str(sender.x) + ' sender.y:' + str(sender.y) def textfield_did_end_editing(self,textfield): #print textfield.text self.word1 = textfield.text self.edit.enabled = False self.edit.text = '' MakeButtonArray()
-
You only need the
global status
statement in those functions where you want to make a permanent change to its value:zippy = 'zippy' def read_only(): print(zippy) # zippy def local_change(): # print(zippy) # would cause the next statement to throw an error zippy = 'pinhead' # local with same name as global print(zippy) # pinhead def permanent_change(): global zippy zippy = 'pinhead' print(zippy) # pinhead read_only() # zippy local_change() # pinhead print(zippy) # zippy -- global was not changed permanent_change() # pinhead print(zippy) # pinhead -- global was changed
Read_only()
demonstrates that even without theglobal
statement, you still have read-only access to the global variable. -
A reformulation of @brumm's code above to:
- Convert
MakeButtonArray
into a subclass ofui.View
- Add
make_edit_text_field()
andmake_button()
methods - Use list comprehension with
enumerate()
to createbutton_array
- Use
format()
to create the commented out debug text
import ui class MakeButtonArray(ui.View): def __init__(self): self.name = 'Buttons' self.background_color = 'grey' self.present('fullscreen') self.edit = self.make_edit_text_field() self.add_subview(self.edit) self.word1 = 'TELEFON' self.button_array = [self.make_button(i, c) for i, c in enumerate(self.word1)] def make_edit_text_field(self): edit_tf = ui.TextField() edit_tf.center = (50, 50) edit_tf.width = self.width edit_tf.height = 30 edit_tf.border_color = 'black' edit_tf.border_width = 1 edit_tf.placeholder = 'single letter or whole word' edit_tf.enabled = False edit_tf.delegate = self edit_tf.clear_button_mode = 'while_editing' return edit_tf def make_button(self, i, c): button = ui.Button(name=str(i), title=c) button.frame = (100, 100 + i * 50, 50, 50) button.bg_color = 'white' button.border_color = 'black' button.border_width = 1 button.action = self.button_array_action self.add_subview(button) return button def button_array_action(self, sender): self.edit.enabled = True self.edit.text = self.word1 #fmt = 'sender.name:{} // "{}" // sender.x:{} sender.y:{}' #self.edit.text = fmt.format(sender.name, sender.title, # sender.x, sender.y) def textfield_did_end_editing(self,textfield): #print textfield.text self.word1 = textfield.text self.edit.enabled = False self.edit.text = '' MakeButtonArray()
- Convert
-
Thanks for the code examples. I'm sure they will help.
-
Hi chaps,
I'm still playing around with buttons for my codeword app. Struggling now with changing the view from one view to another. How is it done please or have I got the wrong end of the stick here?The idea is to firstly select which cells are black. Click done. Then selecting a white cell should present my second class as a view to choose number values from a grid. Trouble is the grid becomes too small when I call it from anywhere but the line not indented!
import console, ui, time crossword_rows = 4 crossword_cols = crossword_rows gap = 1 tupWhite = tuple([1.0,1.0,1.0,1.0]) # white values tupBlack = tuple([1.0,1.0,1.0,1.0]) # black status = 1 def button_tapped1(square): global status #print 'status = '+ str(status) #tup = tuple([1.0,1.0,1.0,1.0]) # white values if status==1: #print 'status = '+ str(status) if str(tupWhite) == str(square.bg_color): square.bg_color = 'black' else: square.bg_color = 'white' elif status==2: #button_tapped2() edit_cell_number(square) print 'step 2' #pass def button_tapped2(button): #add square number global status #print 'status = '+ str(status) status = 2 button.title = 'Colours set.' #print 'status = '+ str(status) #pass def button_tapped3(self, square): pass def edit_cell_number(square): #cyvle through 1 - 26 for cell number if str(tupWhite) == str(square.bg_color): #square.title = str(26) #NumberSelectionGrid() pass class CrosswordView(ui.View): def __init__(self): self.present(hide_title_bar=False ) self.background_color = (0, 1.0, 2.0, 0.4) min_dimension = min(self.width / crossword_cols, self.height / crossword_rows) #self.squares = [] for i in xrange(crossword_cols): x = i * min_dimension for j in xrange(crossword_rows): time.sleep(.05) y = j * min_dimension square = ui.Button(frame = (x, y, min_dimension-gap, min_dimension-gap)) #square.alignment = #ui.ALIGN_CENTER square.bg_color = 'white' #square.title = 'X' self.add_subview(square) square.action = button_tapped1 #button = ui.Button(frame = (x-150, y+80, min_dimension*2, min_dimension*2)) button = ui.Button(frame = (0,0, min_dimension*4, min_dimension*2)) self.add_subview(button) button.title = 'Done' button.background_color = 'white' button.center = (self.width * 0.5, self.height -50) button.flex = 'LRTB' button.action = button_tapped2 #print 'another visit' class NumberSelectionGrid(ui.View): def __init__(self): self.present(hide_title_bar=False ) self.background_color = (0, 1.0, 2.0, 0.4) min_dimension = min(self.width / 5, self.height / 6) #self.squares = [] squareNumber = 1 for i in xrange(5): x = i * min_dimension for j in xrange(6): #time.sleep(.05) y = j * min_dimension square = ui.Button(frame = (x, y, min_dimension-gap, min_dimension-gap)) #square.alignment = #ui.ALIGN_CENTER square.bg_color = 'white' square.title = str(squareNumber) self.add_subview(square) squareNumber = squareNumber +1 square.action = button_tapped3 CrosswordView() NumberSelectionGrid() #NumberSelectionGridd().present(hide_title_bar=False) #NumberSelectionGrid().send_to_back() #print 'status = '+ str(status)