Python unterrichten mit Rougelikes

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
HorstJENS
User
Beiträge: 123
Registriert: Donnerstag 9. Februar 2006, 21:41
Wohnort: Wien, Österreich
Kontaktdaten:

Anbei ein Blogposting von mir welches dafür plädiert, bei Programmier-workshops für Jugendliche, Schnupperstunden etc. anstatt der derzeit sehr modischen visuellen Programmiersprachen wie z.B. Scratch ein wenig Python zu coden, am Beispiel eines rogue-likes im Textmodus (66 Zeilen)

http://spielend-programmieren.at/blog/2 ... ython.html
http://spielend-programmieren.at
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Auch wenn das Programm für Anfänger gedacht ist, muß man nicht schlechte Variablennamen wählen (lines, py, dx). Variablen werden initialisiert, aber nicht verwendet (dx, dy, length). Tuple-Unpacking ist nicht der übliche Weg, Variablen zu initialisieren. Konstanten werden GROSS geschrieben. Die Verwendung von bestimmten Sprachkonstrukten (elif, in) folgt dem Zufallsprinzip.

Und bei über 60 Zeilen Spaghetti-Code wären ein paar Funktionen nicht schlecht.
BlackJack

Hm, „[…] ich empfehle aber Python ab Version 3 zu installieren: Dadurch lassen sich Sonderzeichen und Umlaute problemlos darstellen.“ — komisch, das geht bei mir auch bei Python 2 problemlos. Was mache ich da nur falsch? ;-)

Ich hatte dagegen schon fiese Probleme mit Umlauten/Bytewerten in Python 3 die ich in Python 2 nicht habe. Insbesondere an Stellen wo Python 3 Bytes als Zeichenketten behandelt, wo man das eigentlich nicht machen dürfte. In beiden Sprachversionen muss man letztendlich den Unterschied zwischen Bytes, Zeichenketten, und dem Zusammenhang mit Kodierungen kennen. Und in beiden Sprachversionen muss man an den Übergängen von Textdaten in/aus dem Programm explizit die Kodierung angeben bzw. (de)kodieren, falls man ein robustes, fehlerfreies Programm haben will. Python 2 hat dabei den Vorteil, dass es *sofort* kotzt wenn man versucht sich um das Thema zu drücken, während Python 3 vorgaukelt alles wäre gut, und man fällt dann erst später darüber, dass man sich mal um das Thema Kodierungen hätte kümmern sollen *bevor* man die ganzen Datendateien angelegt hat.
BlackJack

@HorstJENS: Ergänzend zu Sirius3: Tuple unpacking bei der Zuweisung von zusammengehörenden Werten ist okay, also zum Beispiel um Koordinaten die auf zwei Werte aufgeteilt sind, etwas zuzuweisen, aber Du hast da Sachen in einer Zeile stehen die semantisch nicht zusammen gehören, was es damit schwerer macht zu sehen *was* zusammen gehört. `player` und `msg` (besser `message`), gehören beispielsweise nicht zusammen. `player` ist nicht einmal eine Variable sondern eine Konstante. Deren Wert aber trotzdem noch mal literal in einer Zeichenkette auftaucht.

Bei diesen Zuweisungen und dem fehlen sämtlicher Leerzeilen, als wenn das tatsächlich ein grosser Codeabschnitt wäre, der sich nicht sinnvoll gliedern liesse, habe ich so ein bisschen den Eindruck das hier ein Ziel war möglichst wenig Zeilen zu haben. Was oft leichterer Lesbarkeit entgegenläuft, was ich grundsätzlich, aber auch für Anfänger, wichtiger halte als möglichst wenig Zeilen auf Kosten von sinnvoll gesetzten Leerzeilen.

Wenn man so deutlich mehr als eine Sache in eine Funktion, äh, in einen „code blob“ steckt, sind IMHO nicht nur Trennzeilen sinnvoll, sondern auch Kommmentare zu den Abschnitten, denn an Funktions- oder Methodennamen kann man sich ja nicht orientieren.

Code: Alles auswählen

# legend: #=rock  .=floor  f=food  T=treasure                      
DUNGEON = '''
###############################################
#.....fff.#..............#f#.T#.....#.......#T#
#.........#..............###....###.#.....#.#.#
#.........f.....T...f.........##T......f..#...#
###############################################
'''  # add more line to the dungeon!
PLAYER = '@'
PROMPT = 'Type your command or ? and press Enter:'


def main():
    message = 'welcome {}, move with w,a,s,d'.format(PLAYER)
    player_x, player_y = 1, 1
    lines = DUNGEON.split()
    hunger, treasure, food = 0, 0, 7
    while hunger < 100:
        # 
        # Print dungeon.
        # 
        for y, line in enumerate(lines):
            # y is the line number starting with 0      
            if y == player_y:
                print(
                    '{}{}{}'.format(line[:player_x], PLAYER, line[player_x+1:])
                )
            else:
                print(line)
        # 
        # Command processing.
        # 
        status = 'hungry:{} food:{} treasure:{}\n'.format(
            hunger, food, treasure
        )
        command = input('{}\n{}\n{}'.format(message, status, PROMPT))
        message = ''
        delta_x, delta_y = 0, 0
        if command in ['help', '?']:
            message = 'movement: w,a,s,d\neat: e\nquit: exit or quit or q'
        elif command in ['quit', 'exit', 'q']:
            break # exit the game
        elif command == 'e': # eat
            if food > 0:
                message = 'you eat food'
                food -= 1
                hunger -= 11
            else:
                message = 'You have no food!'
        elif command == 'a':
            delta_x = -1  # go left
        elif command == 'd': 
            delta_x = 1   # go right 
        elif command == 'w': 
            delta_y = -1  # go up
        elif command == 's':
            delta_y = 1   # go down
        # 
        # Move player.
        # 
        target = lines[player_y + delta_y][player_x + delta_x]
        if target == '#': 
            delta_x, delta_y = 0, 0 # running into wall
            message = 'ouch, you hit the wall'
        elif target in ['f', 'T']: # food or treasure
            # 
            # TODO Too complex.  Would be automatically simpler if
            #   player position is adjusted before doing this.
            # 
            lines[player_y + delta_y] = (
                lines[player_y + delta_y][:player_x + delta_x]
                + '.'
                + lines[player_y + delta_y][player_x + delta_x + 1:]
            )
            if target == 'f':
                message = 'you found food!'
                food += 1
            if target == 'T':
                message = 'you found treasure!'
                treasure += 1
        # 
        # Update player state.
        # 
        player_x += delta_x #  movement x
        player_y += delta_y #  movement y
        hunger += 1 # getting more hungry
    
    print('Game Over')


if __name__ == '__main__':
    main()
Benutzeravatar
HorstJENS
User
Beiträge: 123
Registriert: Donnerstag 9. Februar 2006, 21:41
Wohnort: Wien, Österreich
Kontaktdaten:

@blackjack
Ja du hast recht Ziel war es möglichst wenig Zeilen zu haben. Einerseits weil diese abgetippt werden müssen von tippfaulen Kindern, haupts!ächlich aber weil der Artikel in einer Zeitschrift veöffentlicht wurde und der Platz arg begrenzt war. Jetzt, für mein blogposting, spielt Platz keine Rolle mehr, ich werde nochmal drüberarbeiten danke für den Hinweis!
Funktion war absichtlich noch keine drin, um möglichst wenig Konzepte erklären zu müssen. Kommt im nächsten Blogposting dran.
http://spielend-programmieren.at
Benutzeravatar
HorstJENS
User
Beiträge: 123
Registriert: Donnerstag 9. Februar 2006, 21:41
Wohnort: Wien, Österreich
Kontaktdaten:

habe den Code nochmals überarbeitet dank der Anregungen von Blackjack und anderen. Zeilenzahl ist von 66 auf 75 gestiegen.

http://spielend-programmieren.at/blog/2 ... ython.html

Funktionen und Klassen gibts im nächsten Blogpost
http://spielend-programmieren.at
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@HorstJENS: jetzt ist nur die Einrücktiefe irgendwie kaputt gegangen.
Benutzeravatar
HorstJENS
User
Beiträge: 123
Registriert: Donnerstag 9. Februar 2006, 21:41
Wohnort: Wien, Österreich
Kontaktdaten:

Sirius3 hat geschrieben:@HorstJENS: jetzt ist nur die Einrücktiefe irgendwie kaputt gegangen.
Danke für den Hinweis, @Sirius3! Bei Github/Gist hatte ich versehentlich Tabs statt Spaces eingestellt beim Listing003, ist inzwischen korrigiert. Dafür rendern jetzt alle 3 Listings links in die Zeilennummern hinein, zumindest bei nicht-eingerückten Zeilen.

Für den Zeitschriftenartikel habe ich Screenshots vom Code-Editor verwendet, auch mühsam.
http://spielend-programmieren.at
BlackJack

Ich konnte es mir mal wieder nicht verkneifen: Aus der Kategorie „Hach, wir hatten ja damals nichts — kein Platz für vernünftige Variablennamen, kein ELSE, …“ hier die Variante die man in den 80ern in Zeitschriften abgedruckt bekommen hätte:
[codebox=locobasic file=Unbenannt.txt] 10 P$="@":DIM D$(40,5):GOSUB 900:M$="WELCOME "+P$+", MOVE WITH W,A,S,D"
20 H$="MOVEMENT: W,A,S,D; EAT: E; QUIT: Q"
30 PX=2:PY=2:H=0:T=0:F=7:PRINT "{CLEAR}";
40 FOR Y=1 TO 5:FOR X=1 TO 40:PRINT D$(X,Y);:NEXT:NEXT:PRINT
100 A$=P$:GOSUB 800
110 PRINT " "
120 PRINT "{UP}";M$
130 PRINT "HUNGRY:";H;" FOOD:";F;" TREASURE";T;" "
140 PRINT "TYPE YOUR COMMAND OR ?:"
150 M$="":DX=0:DY=0
160 GET A$:IF A$="" GOTO 160
170 IF A$="?" THEN M$=H$:GOTO 400
180 IF A$="Q" THEN 500
190 IF A$="E" THEN 600
200 IF A$="A" THEN DX=-1:GOTO 300
210 IF A$="D" THEN DX=1:GOTO 300
220 IF A$="W" THEN DY=-1:GOTO 300
230 IF A$="S" THEN DY=1
300 T$=D$(PX+DX,PY+DY)
310 IF T$="#" THEN DX=0:DY=0:M$="OUCH, YOU HIT THE WALL":GOTO 400
320 IF NOT(T$="F" OR T$="T") THEN 400
330 D$(PX+DX,PY+DY)="."
340 IF T$="F" THEN M$="YOU FOUND FOOD!":F=F+1
350 IF T$="T" THEN M$="YOU FOUND GOLD!":T=T+1
400 A$=".":GOSUB 800:PX=PX+DX:PY=PY+DY:H=H+1:IF H<=100 GOTO 100
500 PRINT "GAME OVER":END
600 IF F<=0 THEN M$="YOU HAVE NO FOOD!":GOTO 400
610 M$="YOU EAT FOOD":F=F-1:H=H-11:GOTO 400
800 PRINT "{HOME}";:FOR I=2 TO PX:PRINT "{RIGHT}";:NEXT
810 FOR I=2 TO PY:PRINT "{DOWN}";:NEXT
820 PRINT A$;"{HOME}{DOWN*6}":RETURN
900 FOR Y=1 TO 5:READ L$:FOR X=1 TO 40:D$(X,Y)=MID$(L$,X,1):NEXT:NEXT:RETURN
910 DATA "########################################"
920 DATA "#....FFF.#...........#F#.T#....#.....#T#"
930 DATA "#........#...........###....##.#...#.#.#"
940 DATA "#........F....T..F........##T....F.#...#"
950 DATA "########################################"[/code]
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Jetzt fehlt nur noch ein BASIC-Programm, welches Maschinenkode in den Speicher poke't und dann derobligatorische SYS-Statement zum Ausführen. So schafft man die 20-Zeiler :D

Wahlweise ging das auch mit PRINT statements und dem anschließenden SYS auf den Bildschirmspeicher.

... damals
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
BlackJack

@bwbg: Wieso die Vergangenheitsform? Das geht auch heute noch so. ;-)

Wobei das Programm jetzt nicht auf Kürze getrimmt ist. Neben den vielen überflüssigen Leerzeichen die da hauptsächlich drin sind, damit die Syntaxhervorhebung funktioniert (die hatte man damals auch nicht), kann man da einiges kürzer formulieren. Beispielsweise kann man die Initialisierung von DX und DY in Zeile 150 weglassen und die Zeilen 200 bis 300 durch die hier ersetzen, schon hätte man vier Zeilen eingespart:

Code: Alles auswählen

200 DX=(A$="A")+-1*(A$="D"):DY=(A$="W")+-1*(A$="S"):T$=D$(PX+DX,PY+DY)
Wird kürzer, aber noch schlechter lesbar als es ohnehin schon ist. :-) Man muss dazu wissen das Vergleiche in BASIC (zumindest in vielen Dialekten) 0 oder -1 als Ergebnis haben, je nachdem ob die Bedingung ”falsch” oder ”wahr” ergibt.
Antworten