Corsi Block Tapping Task

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

Hi
Ich arbeite gerade an der Corsi Block Tapping Task und hänge etwas fest. Ich bin python Anfänger und mein code ist mit Sicherheit nicht perfekt sauber, bitte entschuldigt das. Ich poste den ganzen Code in pastebin, die relevanten Teile aber hier rein.

Ich habe prinzipiell 2 Listen:

1. clicks: Das sind die Klicks des Users in Rechtecken in dieser Form: [(443, 479), (172, 285)]
2. gametime_squares: Das ist die Liste mit den Koordinaten der Sequenz, in der x von 9 Rechtecken aufleuchten, in dieser Form: [[437, 457], [82, 241]]

Ich möchte nun den User Input checken und gucken, ob die Klicks auch innerhalb der richtigen Rechtecke liegen, und auch ob die Reihenfolge stimmt. Sprich, liegt Klick 1 in Rechteck 1 der Sequenz, Klick 2 in Rechteck 2 der Sequenz, etc. (Z.z. ist die Sequenzlänge 2, aber die nimmt mit der Zeit zu). Dafür hab ich folgenden Code:

Code: Alles auswählen

def check_input(): #The coordinates of the list "clicks" must be within (collide) the rectangles of the list "gametime_squares"
    #pprint.pprint((clicks, gametime_squares))
    for cl, sq in zip(clicks, gametime_squares):
        rects = pygame.Rect(sq[0], sq[1], rectSize, rectSize)
        if rects.collidepoint( (cl[0], cl[1]) ):
            print("Correct")
            return True
            
        else:
            print("Incorrect")
            return False
Nun gibt es einige Probleme:

1. Wenn einige Klicks richtig, und andere falsch sind, wird hintereinander "correct" und "incorrect" geprintet, je nach Reihenfolge der richtigen und falschen Klicks.

2. Wenn ich zu viele Klicks mache (z.B. 3 Klicks, obwohl nur 2 Rechtecke aufgeleuchtet haben), und richtig anfange (z.B. die ersten 2 sind zwar korret, aber ich hänge noch einen Klick dran), wird trotzdem "correct" geprintet.

Ich möchte, dass nur wenn genau die richtigen Rechtecke in der richtigen Reihenfolge geklickt werden, correct ausgegeben wird. Alles andere ist falsch.

Danach möchte ich das ganze noch dem User anzeigen, sprich aufzeichnen. Wenn der Input korrekt ist, soll "correct" angezeigt werden, wenn nicht, incorrect. Das Drawn an sich kann ich, aber wie kann ich das Ergebnis von der Funktion oben nutzen, um davon abhängig das richtige zu drawen?

Danke im Voraus!

Hier der ganze Code: http://pastebin.com/UmvsdRKF
Zuletzt geändert von Anonymous am Freitag 20. Januar 2017, 12:43, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@FlyingPersian: Deine Vermutungen, wie sich der Code verhält, sind falsch, da Du immer nur den ersten Klick bewertest. Ad1: statt zu printen brauchst Du eine Funktion, die für einen Klick bewertet, ob ok oder nicht und dann prüfst Du einfach mit einer Schleife ob alle Klicks ok sind. Ad2: Du vergleichst die Anzahl der Klicks mit der Anzahl der Rechtecke. Sollte die nicht übereinstimmen, dann passt was nicht.
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

Okay da fängts schon an. Wie mach ich so eine Funktion? Hast du da evtl. n Link zu nem Tutorial oder so?

Edit: Ich hab das im Python IRC gefragt, und die Funktion wurde mir da von jemandem gegeben:

Code: Alles auswählen

for cl, sq in zip(clicks, gametime_squares):
Und du hast Recht, der checkt nur den ersten Punkt. Dachte eig, dass der durch clicks und gametime_squares loopt und die durchcheckt. Das Ding ist, dass ich nicht weiß, wie ich dem sagen kann, dass der tatsächlich click 1 für Square 1, click 2 für Square 2, etc. macht.

Code: Alles auswählen

for cl in clicks:
        for sq in gametime_squares:
            rects = pygame.Rect(sq[0], sq[1], rectSize, rectSize)
            if rects.collidepoint( (cl[0], cl[1]) ) and len(clicks) == len(gametime_squares): #if length of clicked squares is equal to length of sequence
                print("Correct")
            
            else:
                print("Incorrect")
                return False
Der Code klappt auch nicht. Spuckt mir im Wechsel Correct/Incorrect aus, wenn ichs richtig mache. Argghh
Zuletzt geändert von Anonymous am Freitag 20. Januar 2017, 12:51, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

So ich hab das ganze nochmal umgeschrieben. Die Vierecke sind 100 in Breite und Höhe. Also muss die X-Koordinate vom Punkt größer als die X Koordinate vom Viereck, aber kleiner als die X-Koordinate+100 vom Viereck sein. Das Gleiche für die Y-Koordinate. Richtig?

Code: Alles auswählen

import pygame

clicks = [(450, 150), (360, 450)]
gametime_squares = [[448, 145], [351, 429]]

def test():
    for cl in clicks:
        for sq in gametime_squares:
            print(cl[0])
            print(sq[0])
            if gametime_squares[0] <= cl[0] <= gametime_squares[0]+100 and gametime_squares[1] <= cl[1] <= gametime_squares[1]+100:
                print("Correct")
            else:
                print("Incorrect")


test()
Hier der Output vom Jupyter:
450
448
Incorrect
450
351
Incorrect
360
448
Incorrect
360
351
Incorrect
Das Problem ist, dass der jede Kombination durchcheckt. Das will ich aber nicht. Wie sag ich dem, dass der Click 1 Square 1, Click 2 Square 2, etc. beachten soll?
BlackJack

@FlyingPersian: Warum fängst Du jetzt an `Rect.collidepoint()` selbst zu implementieren?

Die `zip()`-Funktion kennst Du ja bereits. Du solltest Dir auch klar machen wie Schleifen funktionieren und *warum* Du der Code im Moment jeden Klick gegen jedes Rechteck/Quadrat prüft.
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

Hab mir die Dokumentation zu zip() durchgelesen. Der vergleicht also jedes Element zu seinem Gegenüber, also in dem Fall 450 zu 448, 150 zu 145 etc. Sollte dieser Code dann nicht stimmen? Denn 450 liegt zwischen 448 und 448+100, und 150 liegt zwischen 145 und 145+100, und 360 liegt zwischen 351 und 351+100, und 445 liegt zwischen 429 und 429+100. Also sollte der doch jetzt eigentlich "Correct" printen, oder nicht?

Code: Alles auswählen

import pygame

clicks = [(450, 150), (360, 445)]
gametime_squares = [[448, 145], [351, 429]]

def test2():
    for cl, sq in zip(clicks, gametime_squares):
        if gametime_squares[0] <= cl[0] <= gametime_squares[0]+100 and gametime_squares[1] <= cl[1] <= gametime_squares[1]+100:
            print("Correct")
        else:
            print("Incorrect")
            
test2()
edit: Wenn ich vor dem if statement print(cl) und print(sq) einfüge, kommt folgendes:
(450, 150)
[448, 145]
Incorrect
(360, 445)
[351, 429]
Incorrect
Also entweder hab ich einen Denkfehler oder der macht irgendetwas, was ich nicht verstehe. So wie ich das sehe checkt der, ob (450, 150) respektiv zwischen [448, 145] und [448+100, 145+100] liegen, oder?
BlackJack

@FlyingPersian: Das ist nicht der Code den Du ausführst, denn *der* führt zu einem `TypeError`. Je nach Python-Version weil Du da versuchst eine Liste mit einer Zahl zu vergleichen oder zu einer Liste eine Zahl zu addieren. Zeig mal den tatsächlichen Code.

Und schreib echte Funktionen. Eine Funktion sollte, ausser Konstanten, nichts verwenden was nicht als Argument übergeben wurde.
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

Also den TypeError bekomme ich nicht. Hier der gesamte Code:

http://pastebin.com/UmvsdRKF

Die Funktion "def checkinput()" ist aber nicht richtig, da sind noch ältere Versuche drin. Ich hab dann leider keine Ahnung, wie ich das machen soll :/
BlackJack

Ah, okay den bekomme ich tatsächlich auch nicht, aber auch nur weil der Code gar nicht erst bis dort hin kam, was wiederum Zufall ist. Du verwendest also Python 2 und da ist es ein Implementierungsdetail was bei einem ``<=`` Vergleich einer Liste mit einer Zahl heraus kommt. Das kann unwahr sein, aber es kann auch wahr sein. Die Sprachbeschreibung schreibt hier nur vor das es während eines Prozesses immer gleich sein muss das Ergebnis.

Ändert nichts daran das Du in Zeile 8 falsche Sachen vergleichst. Und mit den ``print``-Anweisungen nicht das ausgibst *was* Du vergleichst. Schau Dir noch mal genau an worüber die Schleife iteriert und was Du im ``if`` dann *tatsächlich* vergleichst!
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

Ach Gott, so ein unnötiger Fehler :D sq anstatt gametime_squares natürlich. Dann printet der mir auch das richtige Ergebnis.

Code: Alles auswählen

import pygame
 
clicks = [(540, 150), (500, 445)]
gametime_squares = [[448, 145], [351, 429]]
 
def test2():
    if len(clicks) == len(gametime_squares):
        for cl, sq in zip(clicks, gametime_squares):
            if sq[0] <= cl[0] <= sq[0] + 100 and sq[1] <= cl[1] <= sq[1] + 100:
                print("Correct")
            else:
                print("Incorrect")
    else:
        print("Incorrect")
           
test2()
Hab das ganze nochmal erweitert, um sicher zu gehen, dass auch genug bzw. nicht zu viele Klicks vorhanden sind. Das scheint jetzt soweit richtig zu sein. Jetzt eine andere Frage:

Ich möchte, dass abhängig von dem Ergebnis das passende Feedback für richtig/falsch gezeichnet wird. Das muss man mit return machen, richtig?
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

Also hier nochmal der fertige Code. Der guckt nach, ob die Länge der Klicks gleich ist mit der Anzahl der Vierecke. Wenn ja, dann geht er den Loop durch und vergleicht die Koordinaten. Liegen die Klicks innerhalb des richtigen Vierecks, wird das Ergebnis in der Liste results gespeichert (correct). Wenn nein, wird auch das Ergebnis in der Liste gespeichert (incorrect). Wenn der so oft durchgeschaut hat sie es Klicks gab, dann guckt er, ob das Element "incorrect" in der Liste vorkommt. Wenn ja, dann hat der User etwas falsch gemacht, wenn nein, dann hat er alles richtig gemacht.

Nun möchte ich, dass er mir das finale Ergebnis ausspuckt, damit ich es in der draw_feedback() benutzen kann. Das muss ich mit return machen, soweit ich das sehe. Hab mir gedacht, dass ich eine Variable mache und die sowohl in check_input() als auch in draw_feedback() tue und diese dann auch returne. Aber das klappt irgendwie nicht, bin wahrscheinlich zu müde dafür :D Würde mich dennoch über nen Tipp/Denkanstoß freuen.

[codebox=python file=Unbenannt.txt]def check_input(): #The coordinates of the list "clicks" must be within (collide) the rectangles of the list "gametime_squares"
results = []
if len(clicks) == len(gametime_squares): #check if amount are clicks are equal to current sequence length
while len(results) <= len(clicks): #run this loop until the list "results" is as long as the amount of "clicks"
for cl, sq in zip(clicks, gametime_squares): #compare coordinates to respective squares
if sq[0] <= cl[0] <= sq[0] + 100 and sq[1] <= cl[1] <= sq[1] + 100: #compare coordinates to respective squares
results.append("correct") #adds correct to list results
else:
results.append("incorrect") #adds incorrect to results
else:
results.append("incorrect") #adds incorrect to results
if str("incorrect") in results: #checks whether a click as been incorrect
print("User has input the wrong sequence")
return incorrect
else:
print("User has input the correct sequence")
return correct[/code]
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

Sorry, kann meinen Beitrag hier drüber nicht mehr editieren:

[codebox=pycon file=Unbenannt.txt]feedback = False

def check_input(feedback): #The coordinates of the list "clicks" must be within (collide) the rectangles of the list "gametime_squares"
results = []
if len(clicks) == len(gametime_squares): #check if amount are clicks are equal to current sequence length
while len(results) <= len(clicks): #run this loop until the list "results" is as long as the amount of "clicks"
for cl, sq in zip(clicks, gametime_squares): #compare coordinates to respective squares
if sq[0] <= cl[0] <= sq[0] + 100 and sq[1] <= cl[1] <= sq[1] + 100: #compare coordinates to respective squares
results.append("correct") #adds correct to list results
else:
results.append("incorrect") #adds incorrect to results
else:
results.append("incorrect") #adds incorrect to results
if str("incorrect") in results: #checks whether a click as been incorrect
print("User has input the wrong sequence")
feedback = False
return feedback
else:
print("User has input the correct sequence")
feedback = True
return feedback


def draw_feedback(feedback):
print(feedback)
if feedback == True:
print("It's True!")
screen.fill(BACKGR_COL)
text_surface = font.render("Correct", True, col_white, BACKGR_COL)
text_rectangle = text_surface.get_rect()
text_rectangle.center = (SCREEN_SIZE[0]/2.0,150)
screen.blit(text_surface, text_rectangle)
pygame.display.update()

else:
print("It's False")
screen.fill(BACKGR_COL)
text_surface = font.render("Incorrect", True, col_white, BACKGR_COL)
text_rectangle = text_surface.get_rect()
text_rectangle.center = (SCREEN_SIZE[0]/2.0,150)
screen.blit(text_surface, text_rectangle)
pygame.display.update()[/code]

Also hier spuckt check_input() das richtige raus. Aber wenn ich dann die variabel "feedback" im nächsten Code nutzen will, klappts nicht. Was mach ich hier falsch? Muss ich evtl check_input() in draw_feedback() nutzen?
BlackJack

@FlyingPersian: Das ist ziemlich umständlich. Wie oft wird denn Deiner Meinung nach die ``while``-Schleife durchlaufen?

Dann macht es nicht wirklich Sinn Zeichenketten in einer Liste zusammeln, wenn man doch beim ersten Nicht-Treffer weiss das man das Ergebnis hat, das sich nicht mehr ändern wird.

Was ist der Unterschied zwischen ``"incorrect"`` und ``str("incorrect")``? Was bewirkt die `str()`-Funktion dabei?

Wo kommen die *Namen* `incorrect` und `correct` her? Wo werden die an welche Werte gebunden?

Und Du solltest Dir ganz dringend anschauen wie (echte) Funktionen geschrieben und verwendet werden und das dann auch konsequent machen. Du hast in dem ganzen Programm keine einzige echte Funktion die man separat mal testen könnte. Was beim Entwickeln von Programmen und bei der Fehlersuche wichtig wäre.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Keine Variablen und ``global`` bitte gleich wieder vergessen. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Die hast Du zwar aber es steht trotzdem Code auf Modulebene der dort nicht hingehört. Dann wäre bei der Namensschreibweise die Beachtung des Style Guide for Python Code hilfreich, weil man dann zum Beispiel Konstanten leichter erkennen kann.

Die Hauptfunktion wird üblicherweise durch folgendes Idiom aufgerufen:

Code: Alles auswählen

if __name__ == '__main__':
    main()
Dann kann man das Modul sowohl als Programm ausführen als auch als Modul importieren *ohne* dass das Programm los läuft. Zum Beispiel um einzelne Funktionen automatisiert oder interaktiv in einer Python-Shell zu testen.
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

Ich habe insgesamt 6 Stunden Pythonunterricht gehabt. Das Niveau, auf dem wir arbeiten ist sehr niedrig. Zudem haben wir nie mit solchen "komplexen" Programmen gearbeitet. Die, an denen wir gelernt haben, waren viel viel simpler. Daher sind die Funktionen nicht perfekt, aber ich denke um meine Aufgabe zu erfüllen gut genug.

Die while Schleife wird im ersten und zweiten Durchlauf 2x, im dritten und vierten Durchlauf 3x, etc. durchlaufen.

Okay, das str() kann ich denke ich auch weglassen. War mir unsicher und habs erstmal mit gemacht.
BlackJack

@FlyingPersian: Die Funktionen müssen nicht ”perfekt” sein, aber es müssen echte Funktionen sein, damit sie ihren Zweck erfüllen können: In sich abgeschlossene Teilprobleme zu lösen, ohne undurchsichtige Effekte zu haben weil sie mit irgendwelchen Variablen ausserhalb zu tun haben. Das ist das ”Geheimnis” wie man komplexere Probleme mit ”komplexen” Programmen löst. Man bricht sie auf kleinere Teilprobleme herunter und die dann gegebenenfalls wieder auf kleinere Teilprobleme, solange bis sie so klein sind, das man ein Teilproblem einfach mit einer Funktion mit wenigen Zeilen Code lösen kann. Diese Funktion testet man dann und wenn sie funktioniert, macht man mit der nächsten Teillösung weiter. Funktionen können schon bestehende Teillösungen verwenden. Das macht man dann solange bis das Gesamtproblem gelöst ist.

Du verwendest ja bestehende Funktionen (und Methoden). Keine davon erwartet das man irgendwo bestimmte Namen definiert und sie dann aufruft und sie dann auf diese Namen zugreift. Wenn eine Funktion Werte benötigt, dann übergibt man die als Argumente. Und es gibt da auch keine Funktion die man aufruft und danach ist dann ein bestimmter Name ausserhalb der Funktion an einen Wert gebunden. Wenn eine Funktion ein Ergebnis hat, dann wird das als Rückgabewert an den Aufrufer zurück gegeben, der es dann an einen Namen *seiner* Wahl binden kann. Oder auch gar nicht an einen Namen binden muss, sondern den Wert gleich in einem Ausdruck weiterverwenden kann.

Deine Antwort zur ``while``-Schleife ist falsch. Ich wüsste auch gar nicht was hier x. Durchlauf bedeuten soll, denn die ``while``-Schleife selbst steckt ja gar nicht in einer Schleife. Gib mal Beispielargumente an — ähm, nein das geht ja gar nicht, weil die Funktion keine Argumente bekommt, und man da gar nicht so isoliert darüber Überlegungen anstellen kann. Also gib mal einen Beispielzustand des gesamten Programms an, bei dem die ``while``-Schleife sagen wir mal 2× durchlaufen wird. Ich würde behaupten das wird schwierig. :-)
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

Okay ich werde mich heute mal dransetzen und versuchen, die Funktionen noch weiter zu vereinfachen. Werde mich dann nochmal melden. Danke :)
FlyingPersian
User
Beiträge: 11
Registriert: Freitag 20. Januar 2017, 10:17

So, ich hab jetzt die Funktionen und das Drawen für zwei Funktionen fertig:

1. GETREADY
[codebox=python file=Unbenannt.txt]def main()
#Die Variablen
rectNumb = 9
rectangles = [] #List of rectangles to be drawn
rectSize = 100

#Das Aufrufen der Funktion
if STATE == "PREPARETRIAL": #selects 9 random squares and draws them
create_squares(rectangles, rectNumb, rectSize)

#Die Funktion
def create_squares(a, b, c):
while len(a) <= b:

overlap = False;
pos = [random.randrange(0,SCREEN_SIZE[0]-c -100), random.randrange(0,SCREEN_SIZE[1]-c-100 )] #Added -100 to avoid overlap w/ done buttons
#list of position of drawn squares
for rect in a:
if (rect[0] - c) < pos[0] and pos[0] < (rect[0] + c)\
and (rect[1] - c) < pos[1] and pos[1] < (rect[1] + c):
overlap = True

if overlap == False:
a.append(pos)
print("Creating a square at: " + str(pos))

return a[/code]

2. PREPARETRIAL

Code: Alles auswählen

def main()
    #Variablen
    rectNumb = 9
    rectSize = 100

        #Das Aufrufen der Funktion
        if STATE == "PREPARETRIAL": #selects 9 random squares and draws them
            create_squares(rectangles, rectNumb, rectSize)

#Die Funktion
def draw_prepareTrial(a, b):
    screen.fill(BACKGR_COL)
    for rect in a:
        pygame.draw.rect(screen, col_white, (rect[0],rect[1], b, b), 1) #draw 9 random rectangles

    #Done button
    doneRect = pygame.Rect(900, 650, 100, 50) #define the location and size of your rectangle (left, top, width, height)
    pygame.draw.rect(screen, col_white, doneRect, 5)
    
    #Button text
    text_surface = font_small.render("Done", True, col_white, BACKGR_COL)
    text_rectangle = text_surface.get_rect()
    text_rectangle.center = (950, 675)
    screen.blit(text_surface, text_rectangle)
    
Sind die Funktionen so besser? Hab hier nochmal den Pastebin vom ganzen Code bisher, falls das übersichtlicher ist:

http://pastebin.com/kxDPkqgu
BlackJack

`a`, `b`, und `c` sind keine guten Namen. Man muss ja erst den Code lesen um zu verstehen was die bedeuten und damit was die Funktion als Argumente haben möchte.

Beim `create_squares()` ist die Übergabe einer leeren Liste nicht gut, hier würde man von so einer Funktion eher erwarten, dass sie die Liste selbst erstellt und dann zurück gibt. Beides, also die Liste übergeben *und* zurückgeben macht keinen Sinn weil der Aufrufer die Liste ja bereits hat wenn er sie übergibt.

Das ``return`` gehört auch sicher nicht *in* die Schleife, aber das hast Du wahrscheinlich schon gemerkt.

Ich hatte es ja schon mal geschrieben: Arbeite mit `Rect`-Objekten. Dann sparst Du Dir einiges an Code, weil die nicht nur eine Methode haben die prüft ob zwei `Rect`-Objekte sich überlappen, sondern sogar eine die prüft ob ein `Rect` irgendein `Rect` aus einer Sequenz, zum Beispiel einer Liste, überlappt.
BlackJack

@FlyingPersian: So würde ich die Funktion schreiben (ungetestet):

Code: Alles auswählen

def calculate_max_square_count(rect, square_size):
    squares_per_row = rect.width // square_size
    squares_per_column = rect.height // square_size
    return squares_per_row * squares_per_column


def create_random_square(square_size, rect):
    x = randrange(rect.left, rect.right - square_size)
    y = randrange(rect.top, rect.bottom - square_size)
    return pygame.Rect(x, y, square_size, square_size)


def create_random_squares(count, square_size, rect):
    # 
    # FIXME This test isn't enough because even if the squares would fit
    #   tightly packed they might not when randomly placed, leading to an
    #   endless loop.  We need either a better test, a better algorithm,
    #   or both.
    # 
    if calculate_max_square_count(rect, square_size) < count:
        raise ValueError(
            '{} squares of size {} do not fit into {}'.format(
                count, square_size, rect
            )
        )
    
    result = list()
    while len(result) < count:
        candidate = create_random_square(square_size, rect)
        if not candidate.collidelist(result):
            result.append(candidate)
    return result
Die Bildschirmgrösse und wie viel Platz Schaltflächen einnehmen muss diese Funktion nicht kennen, das ist schon etwas zu speziell. Sie bekommt ein `Rect`-Objekt übergeben innerhalb dessen die Quadrate liegen die erzeugt werden.
Antworten