gui display string manipulation - user-interface

I am learning Python in the hope of impressing my grandchildren - hope nobody is at all ageist! My project is to get the Hangman game I wrote in QuickBasic to work under Windows7.
I have successfuly prepared a Hangedman figure and written routine to extract random word from a large csv file. Now I want to write the user interface but attempts to return to an already used location result in overwriting - I am unable to erase the existing string.
The routine following is to explore how to write a string at a given location and erase it for the purposes of the game:
# attempt to write and erase text at point locations
# all works OK until returned to location already used
#
# 23 FEB 14
from graphics import *
import tkinter
def main():
# make window
win = GraphWin("Display text locator", 600, 500)
# click 1 to start
win.getMouse()
# establish first location
output = Text(Point(400,400),"")
output.draw(win)
# and print simulated hangman word length
output.setText(" _ _ _ _ _ _ _ _ ")
# click 2 for first string replacement
win.getMouse()
output.setText(" ABCZYX W ")
# click 3 to blank out first string
win.getMouse()
output.setText(" ")
# click 4 to show new string
win.getMouse()
output.setText(" and here we are again ")
# click 5 points to new text location and display
win.getMouse()
output = Text(Point(100,100),"")
output.draw(win)
# first string at new location
output.setText(" here's the new position ")
# click 6 to show new string
win.getMouse()
output.setText(" blank that out ")
# click 7 to erase it
win.getMouse()
output.setText(" ")
# click 8 points back to first location
win.getMouse()
output = Text(Point(400,400),"")
output.draw(win)
# should blank out existing string (but fails)
output.setText(" - ")
# click 9 to display new string (but simply overwrites)
win.getMouse()
output.setText(" BACK TO THE FIRST POSITION ")
# click 10 exits
win.getMouse()
win.close()
main()
All suggestions gratefully received (provided no laughing!)
many thanks - jeremy/

Related

How can I use the ruamel.yaml rtsc mode?

I've been working on creating a YAML re-formatter based on ruamel.yaml (which you can see here).
I'm currently using version 0.17.20.
Cleaning up comments and whitespace has been difficult. I want to:
ensure there is only one space before the # for EOL comments
align full line comments with the key or item immediately following
remove duplicate blank lines so there is at most one blank line
To get closer to achieving that, I have a custom Emitter class where I extend write_comment to adjust the comments just before writing with super().write_comment(...). However, the Emitter does not know about which key or item comes next because comments are generally attached as post comments.
As I've studied the ruamel.yaml code to figure out how to do this, I found the rtsc mode (Round Trip Split Comments) which looks fantastic because it separates EOLComment, BlankLineComment and FullLineComment instead of lumping them together.
From what I can tell, the Parser and Scanner have been adjusted to capture the comments. So, loading is (mostly?) implemented with this "NEWCMNT" implementation. But Emitter.write_comment expects CommentToken instead of comment line numbers, so dumping does not work yet.
If I update my Emitter.write_comment method, is that enough to finish dumping? Or what else might be necessary? In one of my tries, I ran into a sys.exit in ScannedComments.assign_eol() - what else is needed to finish that?
PS: I wouldn't normally ask how to collaborate on StackOverflow, but this is not a bug report or a feature request, and I'm trying/failing to use a new (undocumented) feature, so I'm filing this here instead of sourceforge.
rtsc is work in progress cq work started but unfinished. It's internals will almost certainly change.
Two of the three points you indicate can relatively easy be implemented:
set the column of each comment to 0 ( by recursively going over a loaded data structure similar to here ) if the column is before the position of the end of the value on a line, you'll get one space between the value and the column
at the same time doing the recursion in the previous point. Take each comment value and do something like:
value = '\n'.join(line.strip() for line in value.splitlines()
while '\n\n\n' in value:
value = value.replace('\n\n\n', '\n\n')
The indentation to the following element is difficult, depends on the
data structure etc. Given that these are full line comments, I suggest
you do some postprocessing of the YAML document you generate:
find a full line comment, gather full line comments until next line is
not full line comment (i.e. some "real" YAML). Since full line comments
are in column[0] if the previous stuff is applied, you don't have to
track if you are in a (multi-line) literal or folded scalar string where
one of the lines happens to start with #
determine number of spaces
before real YAML and apply these to the full line comments.
import sys
import ruamel.yaml
yaml_str = """\
# the following is a example YAML doc
a:
- b: 42
# collapse multiple empty lines
c: |
# this is not a comment
it is the first line of a block style literal scalar
processing this gobbles a newline which doesn't go into a comment
# that is unless you have a (dedented) comment directly following
d: 42 # and some non-full line comment
e: # another one
# and some more comments to align
f: glitter in the dark near the Tannhäuser gate
"""
def redo_comments(d):
def do_one(comment):
if not comment:
return
comment.column = 0
value = '\n'.join(line.strip() for line in comment.value.splitlines()) + '\n'
while '\n\n\n' in value:
value = value.replace('\n\n\n', '\n\n')
comment.value = value
def do_values(v):
for x in v:
for comment in x:
do_one(comment)
def do_loc(v):
if v is None:
return
do_one(v[0])
if not v[1]:
return
for comment in v[1]:
do_one(comment)
if isinstance(d, dict):
do_loc(d.ca.comment)
do_values(d.ca.items.values())
for val in d.values():
redo_comments(val)
elif isinstance(d, list):
do_values(d.ca.items.values())
for elem in d:
redo_comments(elem)
def realign_full_line_comments(s):
res = []
buf = []
for line in s.splitlines(True):
if not buf:
if line and line[0] == '#':
buf.append(line)
else:
res.append(line)
else:
if line[0] in '#\n':
buf.append(line)
else:
# YAML line, determine indent
count = 0
while line[count] == ' ':
count += 1
if count > len(line):
break # superfluous?
indent = ' ' * count
for cline in buf:
if cline[0] == '\n': # empty
res.append(cline)
else:
res.append(indent + cline)
buf = []
res.append(line)
return ''.join(res)
yaml = ruamel.yaml.YAML()
# yaml.indent(mapping=4, sequence=4, offset=2)
# yaml.preserve_quotes = True
data = yaml.load(yaml_str)
redo_comments(data)
yaml.dump(data, sys.stdout, transform=realign_full_line_comments)
which gives:
# the following is a example YAML doc
a:
- b: 42
# collapse multiple empty lines
c: |
# this is not a comment
it is the first line of a block style literal scalar
processing this gobbles a newline which doesn't go into a comment
# that is unless you have a (dedented) comment directly following
d: 42 # and some non-full line comment
e: # another one
# and some more comments to align
f: glitter in the dark near the Tannhäuser gate

Text outputs as multiple separate lines instead of one paragraph with linebreaks

I have a bot that writes my message to a webpage. I want the message to be sent as one paragraph, but with the lines separated by a linebreak. However, when I actually run the code, the bot inputs and enters each line separately, instead of as one paragraph
I've tried messing with the linebreak formatting and string formatting, but the issue persists
reply_messages = []
reply_messages.push([
"FREE BABY AVOCUDDLE - Thank you for your patience!",
"To redeem your FREE avocuddle, just use the LINK IN OUR BIO and ADD TO CART - just cover shipping, no additional charges!",
"Discount AUTOMATICALLY APPLIES! Super simple, no code!",
"If you order another avocuddle in addition, we cover shipping PLUS the free baby avocuddle! :)",
"Feel free to DM us if you need anything!"
].join("\n")+"\n")
.
.
.
while true
browser.get 'https://twitter.com/messages/requests'
sleep 5
request = wait.until {
el = browser.find_element(:css, "[data-testid='conversation']")
el if el.displayed?
}
break if request.nil?
request.click
not_acceptable_link = true
na_link_index = 1
while not_acceptable_link == true
accept_btn = browser.find_element(:xpath, "//*[contains(text(), 'Accept')]")
unless accept_btn.displayed?
request_2 = wait.until {
el = browser.find_elements(:css, "[data-testid='conversation']")[na_link_index]
el if el.displayed?
}
request_2.click
else
not_acceptable_link = false
end
na_link_index += 1
end
accept_btn = browser.find_element(:xpath, "//*[contains(text(), 'Accept')]")
accept_btn.click
sleep 1
reply_input = wait.until {
el = browser.find_element(:css, "[data-testid='dmComposerTextInput']")
el if el.displayed?
}
reply_input.click
reply_input.send_keys(reply_messages)
.
.
.
I would like for the code to output the entire text as one block of text. However, instead, it outputs as separate lines.
Output currently looks like:
FREE BABY AVOCUDDLE - Thank you for your patience!
(enters this into text box)
To redeem your FREE avocuddle, just use the LINK IN OUR BIO and ADD TO CART - just cover shipping, no additional charges!
(enters this into text box)
etc.
Instead I would like for it to send as one message.

Tool/Algorithm for text comparision after every key hit

I am struggling to find a text comparison tool or algorithm that can compare an expected text against the current state of the text being typed.
I will have an experimentee typewrite a text that he has in front of his eyes. My idea is to compare the current state of the text against the expected text whenever something is typed. That way I want to find out when and what the subject does wrong (I also want to find errors that are not in the resulting text but were in the intermediate text for some time).
Can someone point me in a direction?
Update #1
I have access to the typing data in a csv format:
This is example output data of me typing "foOBar". Every line has the form (timestamp, Key, Press/Release)
17293398.576653,F,P
17293398.6885,F,R
17293399.135282,LeftShift,P
17293399.626881,LeftShift,R
17293401.313254,O,P
17293401.391732,O,R
17293401.827314,LeftShift,P
17293402.073046,O,P
17293402.184859,O,R
17293403.178612,B,P
17293403.301748,B,R
17293403.458137,LeftShift,R
17293404.966193,A,P
17293405.077869,A,R
17293405.725405,R,P
17293405.815159,R,R
In Python
Given your input csv file (I called it keyboard_records.csv)
17293398.576653,F,P
17293398.6885,F,R
17293399.135282,LeftShift,P
17293399.626881,LeftShift,R
17293401.313254,O,P
17293401.391732,O,R
17293401.827314,LeftShift,P
17293402.073046,O,P
17293402.184859,O,R
17293403.178612,B,P
17293403.301748,B,R
17293403.458137,LeftShift,R
17293404.966193,A,P
17293405.077869,A,R
17293405.725405,R,P
17293405.815159,R,R
The following code does the following:
Read its content and store it in a list named steps
For each step in steps recognizes what happened and
If it was a shift press or release sets a flag (shift_on) accordingly
If it was an arrow pressed moves the cursor (index of current where we insert characters) – if it the cursor is at the start or at the end of the string it shouldn't move, that's why those min() and max()
If it was a letter/number/symbol it adds it in curret at cursor position and increments cursor
Here you have it
import csv
steps = [] # list of all actions performed by user
expected = "Hello"
with open("keyboard.csv") as csvfile:
for row in csv.reader(csvfile, delimiter=','):
steps.append((float(row[0]), row[1], row[2]))
# Now we parse the information
current = [] # text written by the user
shift_on = False # is shift pressed
cursor = 0 # where is the cursor in the current text
for step in steps:
time, key, action = step
if key == 'LeftShift':
if action == 'P':
shift_on = True
else:
shift_on = False
continue
if key == 'LeftArrow' and action == 'P':
cursor = max(0, cursor-1)
continue
if key == 'RightArrow' and action == 'P':
cursor = min(len(current), cursor+1)
continue
if action == 'P':
if shift_on is True:
current.insert(cursor, key.upper())
else:
current.insert(cursor, key.lower())
cursor += 1
# Now you can join current into a string
# and compare current with expected
print(''.join(current)) # printing current (just to see what's happening)
else:
# What to do when a key is released?
# Depends on your needs...
continue
To compare current and expected have a look here.
Note: by playing around with the code above and a few more flags you can make it recognize also symbols. This will depend on your keyboard. In mine Shift + 6 = &, AltGr + E = € and Ctrl + Shift + AltGr + è = {. I think this is a good point to start.
Update
Comparing 2 texts isn't a difficult task and you can find tons of pages on the web about it.
Anyway I wanted to present you an object oriented approach to the problem, so I added the compare part that I previously omitted in the first solution.
This is still a rough code, without primary controls over the input. But, as you asked, this is pointing you in a direction.
class UserText:
# Initialize UserText:
# - empty text
# - cursor at beginning
# - shift off
def __init__(self, expected):
self.expected = expected
self.letters = []
self.cursor = 0
self.shift = False
# compares a and b and returns a
# list containing the indices of
# mismatches between a and b
def compare(a, b):
err = []
for i in range(min(len(a), len(b))):
if a[i] != b[i]:
err.append(i)
return err
# Parse a command given in the
# form (time, key, action)
def parse(self, command):
time, key, action = command
output = ""
if action == 'P':
if key == 'LeftShift':
self.shift = True
elif key == 'LeftArrow':
self.cursor = max(0, self.cursor - 1)
elif key == 'RightArrow':
self.cursor = min(len(self.letters), self.cursor + 1)
else:
# Else, a letter/number was pressed. Let's
# add it to self.letters in cursor position
if self.shift is True:
self.letters.insert(self.cursor, key.upper())
else:
self.letters.insert(self.cursor, key.lower())
self.cursor += 1
########## COMPARE WITH EXPECTED ##########
output += "Expected: \t" + self.expected + "\n"
output += "Current: \t" + str(self) + "\n"
errors = UserText.compare(str(self), self.expected[:len(str(self))])
output += "\t\t"
i = 0
for e in errors:
while i != e:
output += " "
i += 1
output += "^"
i += 1
output += "\n[{} errors at time {}]".format(len(errors), time)
return output
else:
if key == 'LeftShift':
self.shift = False
return output
def __str__(self):
return "".join(self.letters)
import csv
steps = [] # list of all actions performed by user
expected = "foobar"
with open("keyboard.csv") as csvfile:
for row in csv.reader(csvfile, delimiter=','):
steps.append((float(row[0]), row[1], row[2]))
# Now we parse the information
ut = UserText(expected)
for step in steps:
print(ut.parse(step))
The output for the csv file above was:
Expected: foobar
Current: f
[0 errors at time 17293398.576653]
Expected: foobar
Current: fo
[0 errors at time 17293401.313254]
Expected: foobar
Current: foO
^
[1 errors at time 17293402.073046]
Expected: foobar
Current: foOB
^^
[2 errors at time 17293403.178612]
Expected: foobar
Current: foOBa
^^
[2 errors at time 17293404.966193]
Expected: foobar
Current: foOBar
^^
[2 errors at time 17293405.725405]
I found the solution to my own question around a year ago. Now i have time to share it with you:
In their 2003 paper 'Metrics for text entry research: An evaluation of MSD and KSPC, and a new unified error metric', R. William Soukoreff and I. Scott MacKenzie propose three major new metrics: 'total error rate', 'corrected error rate' and 'not corrected error rate'. These metrics have become well established since the publication of this paper. These are exaclty the metrics i was looking for.
If you are trying to do something similiar to what i did, e.g. compare the writing performance on different input devices this is the way to go.

Displaying only certain text from file - Visual Basic

I want to only display numbers in a text box that i have. At the moment my code reads the text file and adds all the code to the textbox and not only the needed text(which are numbers).
tbRecipient.Text = My.Computer.FileSystem.ReadAllText("filepath")
if anyone can point me in the right direction and let me know how i would go around this problem, that would be great.
I have a file containing:
Steve, 017876
Alan, 098578
...
I want to list only the numbers into a text box once i have got them from the file.
To do this i am using:
Dim i As Integer
For i = 0 To cbRecipients.CheckedItems.Count - 1
My.Computer.FileSystem.WriteAllText("filepath", cbRecipients.CheckedItems.Item(i) & vbCrLf, True)
Next
frmHome.myFunction()
Then under myFunction() is:
tbRecipient.Text = My.Computer.FileSystem.ReadAllText("filepath")
You can use this function:
Function GetFileColumnContents(s_Path As String, ColumnNumber As Long, ColumnDelimiter As String, Optional s_OutputDelimiter As String) As String
On Error GoTo ErrHandler
Open s_Path For Input As #1 'Open the txt file for readin as Temporary File Number 1
Do While Not EOF(1) 'Read line bu line until end of file
Line Input #1, Mystring 'Store the line value in Mystring
GetFileColumnContents = GetFileColumnContents & s_OutputDelimiter & Split(Mystring, ColumnDelimiter)(ColumnNumber - 1) 'process the string
Loop
ErrHandler:
Close #1
End Function
Call in subrutine:
tbRecipient.Text = GetFileColumnContents("filepath", 2, ",", vbCrLf)
Edit: The linenum = linenum + 1 was not neccesary in the function (edited above)
s_Path : is path to the txt file (like "C:\Test.txt")
ColumnNumber: is the column in the text file. If the data looks like:
A, 123, red
B, 456, blue
then ColumnNumber 1 are letters, ColumnNumber 2 are numbers and 3 are colors.
(ColumnNumber - 1) is there because first part of split has index 0, but the ColumnNumber is 1
split("A, 123, red",",")(0) 'results to "A"
split("A, 123, red",",")(1) 'results to " 123"
If you want to list the items from text file column 2 and in the result separated by comma then call the function with comma as last argument:
tbRecipient.Text = GetFileColumnContents("filepath", 2, ",", ",")

Positional Argument Undefined

I am working on a larger project to write a code so the user can play Connect 4 against the computer. Right now, the user can choose whether or not to go first and the board is drawn. While truing to make sure that the user can only enter legal moves, I have run into a problem where my function legal_moves() takes 1 positional argument, and 0 are given, but I do not understand what I need to do to male everything agree.
#connect 4
#using my own formating
import random
#define global variables
X = "X"
O = "O"
EMPTY = "_"
TIE = "TIE"
NUM_ROWS = 6
NUM_COLS = 8
def display_instruct():
"""Display game instructions."""
print(
"""
Welcome to the second greatest intellectual challenge of all time: Connect4.
This will be a showdown between your human brain and my silicon processor.
You will make your move known by entering a column number, 1 - 7. Your move
(if that column isn't already filled) will move to the lowest available position.
Prepare yourself, human. May the Schwartz be with you! \n
"""
)
def ask_yes_no(question):
"""Ask a yes or no question."""
response = None
while response not in ("y", "n"):
response = input(question).lower()
return response
def ask_number(question,low,high):
"""Ask for a number within range."""
#using range in Python sense-i.e., to ask for
#a number between 1 and 7, call ask_number with low=1, high=8
low=1
high=NUM_COLS
response = None
while response not in range (low,high):
response=int(input(question))
return response
def pieces():
"""Determine if player or computer goes first."""
go_first = ask_yes_no("Do you require the first move? (y/n): ")
if go_first == "y":
print("\nThen take the first move. You will need it.")
human = X
computer = O
else:
print("\nYour bravery will be your undoing... I will go first.")
computer = X
human = O
return computer, human
def new_board():
board = []
for x in range (NUM_COLS):
board.append([" "]*NUM_ROWS)
return board
def display_board(board):
"""Display game board on screen."""
for r in range(NUM_ROWS):
print_row(board,r)
print("\n")
def print_row(board, num):
"""Print specified row from current board"""
this_row = board[num]
print("\n\t| ", this_row[num], "|", this_row[num], "|", this_row[num], "|", this_row[num], "|", this_row[num], "|", this_row[num], "|", this_row[num],"|")
print("\t", "|---|---|---|---|---|---|---|")
# everything works up to here!
def legal_moves(board):
"""Create list of column numbers where a player can drop piece"""
legals = []
if move < NUM_COLS: # make sure this is a legal column
for r in range(NUM_ROWS):
legals.append(board[move])
return legals #returns a list of legal columns
#in human_move function, move input must be in legal_moves list
print (legals)
def human_move(board,human):
"""Get human move"""
legals = legal_moves(board)
print("LEGALS:", legals)
move = None
while move not in legals:
move = ask_number("Which column will you move to? (1-7):", 1, NUM_COLS)
if move not in legals:
print("\nThat column is already full, nerdling. Choose another.\n")
print("Human moving to column", move)
return move #return the column number chosen by user
def get_move_row(turn,move):
move=ask_number("Which column would you like to drop a piece?")
for m in range (NUM_COLS):
place_piece(turn,move)
display_board()
def place_piece(turn,move):
if this_row[m[move]]==" ":
this_row.append[m[move]]=turn
display_instruct()
computer,human=pieces()
board=new_board()
display_board(board)
move= int(input("Move?"))
legal_moves()
print ("Human:", human, "\nComputer:", computer)
Right down the bottom of the script, you call:
move= int(input("Move?"))
legal_moves()
# ^ no arguments
This does not supply the necessary board argument, hence the error message.

Resources