Interactive input in Python for a nethack/pong game hybrid - python

Basically I am trying to build a game of pong that plays in the shell. So far the thing stopping me is the user input. The code has to respond to key presses without having to press Return afterwards. I have tried using msvcrt but it does not yield the result I need. Is it possible to be done without having to code and event listener?
Here is the code:
import os
import msvcrt
#fixed parameters here
resolution_x=16
resolution_y=8
line_position = 4
char_position = 4
def line_draw(char_position, line_length):
#draws a line with # in it marking the ball
line = ""
if char_position == 0:
for i in range(line_length):
line+="_"
print(line)
if char_position:
for i in range(char_position-1):
line+="_"
line+="#"
for j in range(line_length-char_position):
line+="_"
print(line)
def scr_draw(num_lines, line_position, char_position, line_length):
# draws the court with the ball
# line by line with line_draw()
for i in range(line_position):
line_draw(0,line_length)
line_draw(char_position, line_length)
for i in range(num_lines-line_position):
line_draw(0, line_length)
def draw_paddle(line_length, paddle_position):
# this is the paddle positioning
padline=""
for i in range(paddle_position-3):
paddline+="-"
paddline+="==="
for i in range(line_length-paddle_position):
paddline+="-"
print(paddline)
while 1:
#this loop draws everything, then erases it
#so it can draw it again with updates
scr_draw(resolution_y, line_position, char_position, resolution_x) # draw
os.system("CLS") # clears the screen in a really stupid way , to be changed

Related

python-vlc switching playback media between launches

I'm trying to set up a system where my start-screen video loops until 1 of 2 buttons is pressed (GPIO buttons).
Then, the playback changes to either a video with subtitles or no-subtitles.
Once that has finished its play-through, it reverts back to the splash screen video.
I have additional tickers in here just to count the number of play-throughs per day for analytics. My Test device also only has 1 button hooked up which is why GPIO 18 is never used. Implementation will be identical to GPIO 17's, so once one is working the other won't be hard to match up.
Problem
When I launch the script, the media played is not always splash. The script also closes the window at the end of playback, and opens a new one to play the media. I believe this may be due to not establishing an xwindow (for raspberry pi).
Any advice?
#Vars
GPIO.setmode(GPIO.BCM)
GPIO.setup(17,GPIO.IN)
GPIO.setup(18,GPIO.IN)
update = True #Update to false to exit
def Main():
# Setup logs
print(date.today())
# Media Paths
path = "/home/pi/Videos/"
nosubs = path+"Content-NoSubs.mp4"
subs = path+"Content-Subtitles.mp4"
splash = path+"StartScreen.mp4"
Instance = vlc.Instance("-f")
playlist = set([splash,subs,nosubs])
url = [str(splash),str(subs),str(nosubs)] #Yes, this looks pretty redundant. Hopefully it's not.
#Setup the player
player = Instance.media_list_player_new()
Media = Instance.media_new(url[1])
Media_list = Instance.media_list_new(playlist)
Media.get_mrl()
player.set_media_list(Media_list)
playerState = {'State.NothingSpecial',
'State.Opening',
'State.Buffering',
'State.Playing',
'State.Paused',
'State.Stopped',
'State.Ended',
'State.Error'}
subsPlayed = 0
nosubsPlayed = 0
active = 0
playingMedia = 0
while update:
input = GPIO.input(17)
state = str(player.get_state())
if(state == playerState[0]):
player.play_item_at_index(0)
player.set_playback_mode(2)
if(state == playerState[7]):
player.play_item_at_index(0)
playingMedia = 0
if input == 1 and playingMedia == 0:
playingMedia = 1
player.play_item_at_index(1)
active +=1
nosubsPlayed +=1
print(playingMedia)
with open(str(date.today()))+'.txt','w' as file:
file.write("Active Views: " + active)
file.write("SubsPlayed: " + subsPlayed)
file.write("No Subs Played: " + nosubsPlayed)
Main()
So I figured out the solution, but not the problem's origin.
# Make my media paths into vlc.Media Objects
nosubs = vlc.Media(path+"Content-NoSubs.mp4")
subs = vlc.Media(path+"Content-Subtitles.mp4")
splash = vlc.Media(path+"SplashScreen.mp4")
#Setup the player
player = Instance.media_list_player_new()
Media_list = Instance.media_list_new()
Media_list.add_media(splash)
Media_list.add_media(subs)
Media_list.add_media(nosubs)
player.set_media_list(Media_list)
Media_list.lock()
Setting up each of the media by name in my list helps by switching the play function from play_item_at_index(int) to play_item(media)
Still not sure why it was kind of randomizing. My guess was that it changed the position of media in the list based on play through.
My next step will be to adjust this to work off of media_player and embedding playback into a tkinter window.

Stopping a function based on a value

I am running a python script on a raspberry-pi.
Essentially, I would like a camera to take a picture every 5 seconds, but only if I have set a boolean to true, which gets toggled on a physical button.
initially I set it to true, and then in my while(true) loop, I want to check to see if the variable is set to true, and if so, start taking pictures every 5 seconds. The issue is if I use something like time time.sleep(5), it essentially freezes everything, including the check. Combine that with the fact that I am using debouncing for the button, it then becomes impossible for me to actually toggle the script since I would have to press it exactly after the 5s wait time, right for the value check... I've been searching around and I think the likely solution would have to include threading, but I can't wrap my head around it. One kind of workaround I thought of would be to look at the system time and if the seconds is a multiple of 5, then take picture (all within the main loop). This seems a bit sketchy.
Script below:
### Imports
from goprocam import GoProCamera, constants
import board
import digitalio
from adafruit_debouncer import Debouncer
import os
import shutil
import time
### GoPro settings
goproCamera = GoProCamera.GoPro()
### Button settings
pin = digitalio.DigitalInOut(board.D12)
pin.direction = digitalio.Direction.INPUT
pin.pull = digitalio.Pull.UP
switch = Debouncer(pin, interval=0.1)
save = False #this is the variable
while(True):
switch.update()
if switch.fell:
print("Pressed, toggling value")
save = not save
if save:
goproCamera.take_photo()
goproCamera.downloadLastMedia()
time.sleep(5)
Here's something to try:
while(True):
switch.update()
if switch.fell:
print("Pressed, toggling value")
save = not save
if save:
current_time = time.time()
if current_time - last_pic_time >= 5:
goproCamera.take_photo()
goproCamera.downloadLastMedia()
last_pic_time = current_time
Depending on exactly what sort of behavior you want, you may have to fiddle with when and how often time.time() is called.
Cheers!
Maybe something like this?
import threading
def set_interval(func, sec):
def func_wrapper():
set_interval(func, sec)
func()
t = threading.Timer(sec, func_wrapper)
t.start()
return t
We call the function above inside the main loop.
Wrap your while loop content on a function:
def take_photo:
goproCamera.take_photo()
goproCamera.downloadLastMedia()
Now we create a flag initially set to False to avoid creating multiple threads.
Notice that I did this before the while loop. We just need a starting value here.
active = False
while(True):
switch.update()
if switch.fell:
print("Pressed, toggling value")
save = not save
if save: # we need to start taking photos.
if not active: # it is not active... so it is the first time it is being called or it has been toggled to save as True again.
photo_thread = set_interval(take_photo, 5) # grabbing a handle to the thread - photo_thread - so we can cancel it later when save is set to False.
active = True # marking as active to be skipped from the loop until save is False
else:
try: # photo_thread may not exist yet so I wrapped it inside a try statement here.
photo_thread.cancel() # if we have a thread we kill it
active = False #setting to False so the next time the button is pressed we can create a new one.
Let me know if it works. =)
What I ended up doing:
### Imports
from goprocam import GoProCamera, constants
import board
import digitalio
from adafruit_debouncer import Debouncer
import os
import time
import threading
### GoPro settings
gopro = GoProCamera.GoPro()
### Button settings
pin = digitalio.DigitalInOut(board.D12)
pin.direction = digitalio.Direction.INPUT
pin.pull = digitalio.Pull.UP
switch = Debouncer(pin, interval=0.1)
### Picture save location
dir_path = os.path.dirname(os.path.realpath(__file__))
new_path = dir_path+"/pictures/"
save = False
### Functions
def takePhoto(e):
while e.isSet():
gopro.take_photo()
gopro.downloadLastMedia()
fname = '100GOPRO-' + gopro.getMedia().split("/")[-1]
current_file = dir_path+'/'+fname
if os.path.isfile(current_file):
os.replace(current_file, new_path+fname) #move file, would be cleaner to download the file directly to the right folder, but the API doesn't work the way I thought it did
e.wait(5)
### Initial settings
e = threading.Event()
t1 = threading.Thread(target=takePhoto, args=([e]))
print("Starting script")
while(True):
switch.update()
if switch.fell:
#toggle value
save = not save
if save:
e.set() #should be taking pictures
else:
e.clear() #not taking pictures
if not t1.is_alive(): #start the thread if it hasn't been yet
if e.is_set():
t1.start()

Play a random sequence of 4 sounds while a video is played in Psychopy?

I'm trying to create an experiment using Psychopy.
In the specific I'm trying to create a routine ("trial") where a video ("movie1") is presented and at the same time I would like to play a sequence of 4 sounds (one per second) randomly chosen from a list of 10 in an excel file (sounds.routine.xlsx).
Here's what I have done so far:
from __future__ import absolute_import, division
from psychopy import locale_setup
from psychopy import prefs
from psychopy import sound, gui, visual, core, data, event, logging, clock
from psychopy.constants import (NOT_STARTED, STARTED, PLAYING, PAUSED,
STOPPED, FINISHED, PRESSED, RELEASED, FOREVER)
import numpy as np # whole numpy lib is available, prepend 'np.'
from numpy import (sin, cos, tan, log, log10, pi, average,
sqrt, std, deg2rad, rad2deg, linspace, asarray)
from numpy.random import random, randint, normal, shuffle
import os # handy system and path functions
import sys # to get file system encoding
from psychopy.hardware import keyboard
# Ensure that relative paths start from the same directory as this script
_thisDir = os.path.dirname(os.path.abspath(__file__))
os.chdir(_thisDir)
# Store info about the experiment session
psychopyVersion = '3.2.4'
expName = 'dsffdsfads' # from the Builder filename that created this script
expInfo = {'participant': '', 'session': '001'}
dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName)
if dlg.OK == False:
core.quit() # user pressed cancel
expInfo['date'] = data.getDateStr() # add a simple timestamp
expInfo['expName'] = expName
expInfo['psychopyVersion'] = psychopyVersion
# Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc
filename = _thisDir + os.sep + u'data/%s_%s_%s' % (expInfo['participant'], expName, expInfo['date'])
# An ExperimentHandler isn't essential but helps with data saving
thisExp = data.ExperimentHandler(name=expName, version='',
extraInfo=expInfo, runtimeInfo=None,
originPath='/Users/Documents/dsffdsfads.py',
savePickle=True, saveWideText=True,
dataFileName=filename)
# save a log file for detail verbose info
logFile = logging.LogFile(filename+'.log', level=logging.EXP)
logging.console.setLevel(logging.WARNING) # this outputs to the screen, not a file
endExpNow = False # flag for 'escape' or other condition => quit the exp
frameTolerance = 0.001 # how close to onset before 'same' frame
# Start Code - component code to be run before the window creation
# Setup the Window
win = visual.Window(
size=(1024, 768), fullscr=True, screen=0,
winType='pyglet', allowGUI=False, allowStencil=False,
monitor='testMonitor', color=[0,0,0], colorSpace='rgb',
blendMode='avg', useFBO=True,
units='height')
# store frame rate of monitor if we can measure it
expInfo['frameRate'] = win.getActualFrameRate()
if expInfo['frameRate'] != None:
frameDur = 1.0 / round(expInfo['frameRate'])
else:
frameDur = 1.0 / 60.0 # could not measure, so guess
# create a default keyboard (e.g. to check for escape)
defaultKeyboard = keyboard.Keyboard()
# Initialize components for Routine "trial"
trialClock = core.Clock()
sound1 = sound.Sound(Sounds, secs=-1, stereo=True, hamming=True,
name='sound1')
sound1.setVolume(1)
movie1 = visual.MovieStim3(
win=win, name='movie1',
noAudio = True,
filename='Movies/Random_4.mp4',
ori=0, pos=(0, 0), opacity=1,
loop=False,
depth=-1.0,
)
from np.random import choice
# Create some handy timers
globalClock = core.Clock() # to track the time since experiment started
routineTimer = core.CountdownTimer() # to track time remaining of each (non-slip) routine
# set up handler to look after randomisation of conditions etc
trials = data.TrialHandler(nReps=1, method='random',
extraInfo=expInfo, originPath=-1,
trialList=data.importConditions('../Desktop/Countingpuppet/sounds_routine.xlsx', selection=choice(10, size = 4, replace = False)),
seed=None, name='trials')
thisExp.addLoop(trials) # add the loop to the experiment
thisTrial = trials.trialList[0] # so we can initialise stimuli with some values
# abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
if thisTrial != None:
for paramName in thisTrial:
exec('{} = thisTrial[paramName]'.format(paramName))
for thisTrial in trials:
currentLoop = trials
# abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
if thisTrial != None:
for paramName in thisTrial:
exec('{} = thisTrial[paramName]'.format(paramName))
# ------Prepare to start Routine "trial"-------
# update component parameters for each repeat
sound1.setSound(Sounds, hamming=True)
sound1.setVolume(1, log=False)
# keep track of which components have finished
trialComponents = [sound1, movie1]
for thisComponent in trialComponents:
thisComponent.tStart = None
thisComponent.tStop = None
thisComponent.tStartRefresh = None
thisComponent.tStopRefresh = None
if hasattr(thisComponent, 'status'):
thisComponent.status = NOT_STARTED
# reset timers
t = 0
_timeToFirstFrame = win.getFutureFlipTime(clock="now")
trialClock.reset(-_timeToFirstFrame) # t0 is time of first possible flip
frameN = -1
continueRoutine = True
# -------Run Routine "trial"-------
while continueRoutine:
# get current time
t = trialClock.getTime()
tThisFlip = win.getFutureFlipTime(clock=trialClock)
tThisFlipGlobal = win.getFutureFlipTime(clock=None)
frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
# update/draw components on each frame
# start/stop sound1
if sound1.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
# keep track of start time/frame for later
sound1.frameNStart = frameN # exact frame index
sound1.tStart = t # local t and not account for scr refresh
sound1.tStartRefresh = tThisFlipGlobal # on global time
sound1.play(when=win) # sync with win flip
# *movie1* updates
if movie1.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
# keep track of start time/frame for later
movie1.frameNStart = frameN # exact frame index
movie1.tStart = t # local t and not account for scr refresh
movie1.tStartRefresh = tThisFlipGlobal # on global time
win.timeOnFlip(movie1, 'tStartRefresh') # time at next scr refresh
movie1.setAutoDraw(True)
# check for quit (typically the Esc key)
if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
core.quit()
# check if all components have finished
if not continueRoutine: # a component has requested a forced-end of Routine
break
continueRoutine = False # will revert to True if at least one component still running
for thisComponent in trialComponents:
if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
continueRoutine = True
break # at least one component has not yet finished
# refresh the screen
if continueRoutine: # don't flip if this routine is over or we'll get a blank screen
win.flip()
# -------Ending Routine "trial"-------
for thisComponent in trialComponents:
if hasattr(thisComponent, "setAutoDraw"):
thisComponent.setAutoDraw(False)
sound1.stop() # ensure sound has stopped at end of routine
trials.addData('sound1.started', sound1.tStartRefresh)
trials.addData('sound1.stopped', sound1.tStopRefresh)
trials.addData('movie1.started', movie1.tStartRefresh)
trials.addData('movie1.stopped', movie1.tStopRefresh)
# the Routine "trial" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()
thisExp.nextEntry()
# completed 1 repeats of 'trials'
# Flip one final time so any remaining win.callOnFlip()
# and win.timeOnFlip() tasks get executed before quitting
win.flip()
# these shouldn't be strictly necessary (should auto-save)
thisExp.saveAsWideText(filename+'.csv')
thisExp.saveAsPickle(filename)
logging.flush()
# make sure everything is closed down
thisExp.abort() # or data files will save again on exit
win.close()
core.quit()
The problem is that using np.choice only one number is reproduced and not the entire sequence of four randomly chosen numbers without repetitions. How can I do this?
Thanks in advance
Not tested, but something like this:
FPS = 60 # Frame rate of your monitor
from random import choice
from psychopy import visual, sound
win = visual.Window()
movie = visual.MovieStim(win, 'my_file.avi')
sounds = [sound.Sound('sound1.wav'), sound.Sound('sound2.wav'), sound.Sound('sound3.wav'), sound.Sound('sound4.wav')]
frame = 1
while movie.status != visual.FINISHED:
movie.draw() # Show the next frame of the movie
if frame % FPS == 0: # If a second has passed
choice(sounds).play() # Play a random sound

This is much too slow

I have the following code working very nicely, but it is WAY too slow - something like 3/4 of a second latency. This is running on a Raspberry Pi 3+ under Raspbian Stretch with chromium browser as the client and apache2 as the server, both on the same Pi. There is a 5" monitor, but no keyboard or mouse attached. The unit has four momentary switches attached to GPIO pins, and each of the buttons controls a cursor. Two of the buttons move the cursor image up and down, and the other two control the text on top of the cursor image. After each button press, I issue a keypress (F5, in the code below) to the browser using pynput.keyboard. I am sure the reason this is too slow is at the moment I am refreshing the entire screen after updating control values in files in /run/thermostat. A better solution would be to emulate UpArrow and DnArrow to have the highlight image moved or the text updated, as the case may be. Sending the appropriate control code is easy, but how do I move the image or change the text without doing a full page refresh? (Note, this would also allow me to use the keyboard from an external browser to do the same thing.)
Snippet of external Python script that monitors the button status:
import RPi.GPIO as GPIO
import time, os
from pins import pinsarrow
from pynput.keyboard import Key, Controller
keyboard = Controller()
...
def my_callback4(channel):
Exists = os.path.exists('/run/thermostat/Cursor') # Check to see Cursor position is set
if Exists:
Fsize = os.path.getsize('/run/thermostat/Cursor') # Check for a valid file
if Fsize == 0:
Write_Cursor(0)
Cursor = Read_Cursor()
if Cursor > 0:
Cursor = Cursor - 1
Write_Cursor(Cursor) # Write the new Cursor position
##################################################
keyboard.press(Key.f5) #
keyboard.release(Key.f5) #
##################################################
else:
Write_Cursor(0) # File does not exist. Write the Cursor file.
GPIO.add_event_detect(pinsarrow[0], GPIO.RISING, callback=my_callback1, bouncetime=100)
GPIO.add_event_detect(pinsarrow[1], GPIO.RISING, callback=my_callback2, bouncetime=100)
GPIO.add_event_detect(pinsarrow[2], GPIO.RISING, callback=my_callback3, bouncetime=100)
GPIO.add_event_detect(pinsarrow[3], GPIO.RISING, callback=my_callback4, bouncetime=100)
Snippet of Python code that creates the web page:
Exists = os.path.exists('/run/thermostat/Cursor') # Check to see the Cursor position has been set
if Exists:
Fsize = os.path.getsize('/run/thermostat/Cursor')
if Fsize > 0:
with open("/run/thermostat/Cursor","r") as f: # Get the Cursor position
Cursor = int(f.read())
else:
write_Cursor() # No value in Cursor file
else:
write_Cursor() # Cursor file does not exist
RunStatus = ["Heating On", "Cooling On", "Fan On", "Idle"]
print('Content-type: text/html\n')
print("\n")
with open("/usr/lib/cgi-bin/index.txt","r") as f:
x = f.read()
print(x)
######################################################################
## Places the cursor image at a position specified by variable Cursor#
######################################################################
print('<img src="/images/Thermostat-Cursor.png" width="100" height="29" style="position: absolute; left: 15px; top: ',65 + 30 * Cursor,'px;">', sep='')
############################################################
## Check for cursor position and set color text accordingly#
############################################################
bColor = ["0", "0", "0", "0", "0", "0", "0"]
for x in range(0, 7):
if Cursor == x:
bColor[x] = "black"
else:
bColor[x] = "rgb(200,200,200)"
## Print each <div> with appropriate color
print('<div style="color:',bColor[0],'" class="Scale">',ClassValues[0][LineValue[0]],'</div>', sep="")
print('<div style="color:',bColor[1],'" class="Clock">',ClassValues[1][LineValue[1]],'</div>', sep="")
print('<div style="color:',bColor[2],'" class="Barometer">',ClassValues[2][LineValue[2]],'</div>', sep="")
print('<div style="color:',bColor[3],'" class="Server">',ClassValues[3][LineValue[3]],'</div>', sep="")
print('<div style="color:',bColor[4],'" class="Control">',ClassValues[4][LineValue[4]],'</div>', sep="")
if LineValue[4] < 2:
print('<div style="color:',bColor[5],'" class="Hot">Cool: ',ClassValues[5],'</div>', sep="")
if LineValue[4] == 0 or LineValue[4] == 2:
print('<div style="color:',bColor[6],'" class="Cold">Heat: ',ClassValues[6],'</div>', sep="")

Coding multiple lists and Loops

For my experiment, I am creating a simple reaction time test. It will have 4 blocks, with 6 trials in each. I have managed to present the first trial of a block on screen, albeit for a practice round (the stimuli does appear however). However, I am not sure whether I should be creating a number of lists (i.e. to hold the different stimuli for the different trials) or if I should be altering the characteristics of my one existing loop. My code is below:
from psychopy import visual, core, event #import some libraries from PsychoPy
import psychopy.event
#Create the code for saving the data at some point
#create a window
mywin = visual.Window([1920,1080], monitor="testMonitor", units="deg")
mywin.update()
# create the six stimuli:
Target = visual.TextStim(mywin, text = "text default")
text_stim_list = [] # a list to hold them
Stim_text = ["Berlin", "Paris", "London", "Nice", "Vienna", "Charming"] #
their content
#the positions of the stimuli
positions = [
(-10, 10),
(10, 10),
(-10, -10),
(10, -10),
(-1, -10),
(1, 10),
]
#Initial message to participants about the study
message1 = visual.TextStim(mywin, text = "You have been captured by the
plotters. As a test to see if you have information regarding the event, an
on screen test will be adminstered. Press Spacebar when ready")
message1.draw()
mywin.update()
#this will wait for a button press confirmation from p's to continue
response = event.waitKeys(keyList = ['space',], timeStamped = True)
#Initial message to participants about the study
message1 = visual.TextStim(mywin, text = "On the next screen, you will be
presented with a practice round of stimuli. Press Q if any of the cities are
familiar, or press P if you do not recognise the cities")
message1.draw()
mywin.update()
#this will wait for a button press confirmation from p's to continue
response = event.waitKeys(keyList = ['space',], timeStamped = True)
#Loop for drawing the stimulus
#code for the keyboard response
keys = psychopy.event.waitKeys(keyList=["q", "p"])
#Practice round-should it be stopped by a button press?
for frameN in range(7*60):
# draw each stim *on each frame*
for i in range(len(Stim_text)):
Target.setPos(positions[i])
Target.setText(Stim_text[i])
Target.draw()
# now flip the window (after all stimuli drawn)
mywin.flip()
#Initial message to participants about the study
message1 = visual.TextStim(mywin, text = "On the next screen, the cities
which
our intelligence tells us are at risk will be presented. Press Q if any are
familiar, press P if not blah blah blah.. press space when ready")
message1.draw()
mywin.update()
response = event.waitKeys(keyList = ['space',], timeStamped = True)#wait for
subjects to state they are ready
#Cities block
#First trial
print Stim_text
del Stim_text[1,2]
print Stim_text
I am fairly sure it will involve changing the attributes of the loop- as otherwise it would not make sense to have so many lists.
Nathan
In general, I would do something like this:
# Run the code inside this loop four times
for block in range(4):
for i, position in enumerate(positions):
# Prepare stimulus
Target.pos = position
Target.text = Stim_text[i]
# Show it
Target.draw()
win.flip()
# Collect response and score it
key = event.waitKeys(keyList=['q', 'p'])[0] # Just get the first response key
# Save response here somehow.
As Mike pointed out in a comment, look into psychopy.data.TrialHandler which can do much of this for you, including saving data. Also, there's no need to generate a new visual.TextStim for each message. Just update the text of an existing one with the desired text.

Categories