Tag 1:
Warum eigentlich die Eingabe von "Q"? Wofür steht das? "B" steht für Buchstabe nehme ich an. "Q" für Quit? Dann wäre das Denglisch auch in der Benutzerinteraktion.
Code: Alles auswählen
#!/usr/bin/env python3
import random
from string import ascii_uppercase
def main():
while True:
answer = input(
"Gib Q zum beenden oder B für einen zufälligem Buchstaben ein: "
).lower()
if answer == "q":
break
if answer == "b":
number = random.randint(1, 26)
print(
f"{ascii_uppercase[number - 1]}"
f" ({number}. Buchstabe im Alphabet)"
)
if __name__ == "__main__":
main()
Tag 2:
Code: Alles auswählen
#!/usr/bin/env python3
import random
from string import ascii_uppercase
def main():
while True:
word = input("Gib ein beliebiges Wort ein: ").upper()
total_points = 0
for _ in range(10):
answer = input(
"Gib Q zum beenden oder B für einen zufälligem Buchstaben ein: "
).lower()
if answer == "q":
return
if answer == "b":
letter = random.choice(ascii_uppercase)
count = word.count(letter)
points = count * 10
print(
f"{letter!r} ist {count} mal enthalten,"
f" das gibt {points} Punkte."
)
total_points += points
print(f"Die Gesamtpunktzahl beträgt {total_points} Punkte.")
if __name__ == "__main__":
main()
Tag 3:
Wie schon gesagt sehe ich hier ein Problem damit, dass man für zwei Spieler anfangen muss unschönen Code zu schreiben wenn Datenklassen und Listen nicht ”erlaubt” sind. Das ganze wird durch die Zusatzbedingung, dass sich das bei einem einzelnen Spieler auch noch leicht anders verhalten soll als bei mehreren Spielern, nicht besser. Meine Lösung deshalb mit Datenklasse und Liste und allgemein für jede positive Anzahl von Spielern.
Code: Alles auswählen
#!/usr/bin/env python3
import random
from itertools import groupby
from operator import attrgetter
from string import ascii_uppercase
class Player:
def __init__(self, name, points=0):
self.name = name
self.points = points
def main():
while True:
player_count = int(input("Gib die Anzahl der Spieler ein: "))
if player_count >= 1:
break
print("Die Anzahl muss ≥1 sein!")
if player_count == 1:
player_names = ["Du"]
else:
player_names = [
input(f"Gib Namen von Spieler {i + 1} ein: ")
for i in range(player_count)
]
while True:
word = input("Gib ein beliebiges Wort ein: ").upper()
players = list(map(Player, player_names))
for _ in range(10):
for player in players:
print(f"Aktueller Spieler: {player.name}.")
answer = input(
"Gib Q zum beenden oder B für einen"
"zufälligem Buchstaben ein: "
).lower()
if answer == "q":
return
if answer == "b":
letter = random.choice(ascii_uppercase)
count = word.count(letter)
points = count * 10
print(
f"{letter!r} ist {count} mal enthalten,"
f" das gibt {points} Punkte."
)
player.points += points
get_key = attrgetter("points")
points, winners = next(
groupby(sorted(players, key=get_key, reverse=True), key=get_key)
)
winners_text = ", ".join(winner.name for winner in winners)
print(f"Gewinner mit {points} Punkten: {winners_text}.")
if __name__ == "__main__":
main()
Ja, Anfänger werden eher nicht `attrgetter()` und `groupby()` kennen, aber das alles zu Fuss zu programmieren wäre ziemlich aufwändig. Der Fall der damit abgedeckt wird, ist in der Beispiellösung übrigens unbehandelt, beziehungsweise drückt sich die Beispiellösung um den Aufgabenpunkt 5).
Und mir das auch so schon zu lang für eine Funktion. Wenn man das tatsächlich mit zwei Variablen pro Spieler und dann jeweils mit ``if``/``else`` und dann im Grunde den gleichen Code für jeden der beiden Spieler löst, wäre es noch mal länger, und wenn man noch Eingabeüberprüfungen einbaut, beispielsweise um sicherzustellen, dass jeder Name nur einmal vergeben werden kann, würde das noch mal länger.
Ich ermuntere Anfänger immer kleine Funktionen zu schreiben. Es ist einfacher später mehrere kleine Funktionen zusammenzufassen, falls die einem nicht genug tun und nur an einer Stelle im Programm aufgerufen werden, als erst einen grossen Haufen Code zu schreiben, und den dann auf Funktionen aufzuteilen, wenn er einem über den Kopf wächst.
Programmteile lassen sich auch leichter testen wenn sie in kleinen Funktionen stecken. Wenn man hier beispielsweise testen wollen würde ob das mit dem Gewinner ermitteln am Ende klappt — Anfänger werden das ja „zu Fuss“ programmieren — müsste man immer das Spiel durchspielen und hat keinen Einfluss auf die erreichten Punkte pro Spieler. Wenn das in einer Funktion stecken würde, welche die Spieler übergeben bekommt, könnte man sich zum Testen einfach ein paar Spieler mit Punkten erstellen, und die Funktion aufrufen.
Tag 4:
Juhuu, wir können Funktionen benutzen. Gleich mal das Hauptprogramm ein bisschen schlanker machen. Und ich habe da das Q und B eingeben raus genommen, weil das echt besch…eiden zu bedienen ist immer noch mal B eingeben zu müssen. Abbruch geht ja immer mit Strg+C.
Code: Alles auswählen
#!/usr/bin/env python3
import random
from itertools import groupby
from operator import attrgetter
WORDS_FILENAME = "wortliste.txt"
class Player:
def __init__(self, name, points=0):
self.name = name
self.points = points
def read_random_word(filename):
with open(filename, encoding="utf-8") as lines:
return random.choice(list(lines)).strip().upper()
def ask_player_count():
while True:
player_count = int(input("Gib die Anzahl der Spieler ein: "))
if player_count >= 1:
return player_count
print("Die Anzahl muss ≥1 sein!")
def ask_player_names(player_count):
if player_count == 1:
return ["Du"]
return [
input(f"Gib Namen von Spieler {i + 1} ein: ")
for i in range(player_count)
]
def ask_letter():
while True:
letter = input("Gib einen Buchstaben ein: ").upper()
if len(letter) == 1:
return letter
print("Bitte genau einen Buchstaben eingeben!")
def play_round(players, word):
for player in players:
print(f"Aktueller Spieler: {player.name}.")
letter = ask_letter()
count = word.count(letter)
points = count * 10
print(
f"{letter!r} ist {count} mal enthalten, das gibt {points} Punkte."
)
player.points += points
def get_winners(players):
get_key = attrgetter("points")
return next(
groupby(sorted(players, key=get_key, reverse=True), key=get_key)
)
def main():
player_names = ask_player_names(ask_player_count())
while True:
word = read_random_word(WORDS_FILENAME)
print("Es wurde ein zufälliges Wort ermittelt.")
players = list(map(Player, player_names))
for _ in range(10):
play_round(players, word)
print(f"Das Wort lautet {word!r}.")
points, winners = get_winners(players)
winners_text = ", ".join(winner.name for winner in winners)
print(f"Gewinner mit {points} Punkten: {winners_text}.")
if __name__ == "__main__":
main()
Tag 5:
Nix besonderes.
Code: Alles auswählen
#!/usr/bin/env python3
import random
from itertools import groupby
from operator import attrgetter
WORDS_FILENAME = "wortliste.txt"
class Player:
def __init__(self, name, points=0):
self.name = name
self.points = points
def read_random_word(filename):
with open(filename, encoding="utf-8") as lines:
return random.choice(list(lines)).strip().upper()
def ask_player_count():
while True:
player_count = int(input("Gib die Anzahl der Spieler ein: "))
if player_count >= 1:
return player_count
print("Die Anzahl muss ≥1 sein!")
def ask_player_names(player_count):
if player_count == 1:
return ["Du"]
return [
input(f"Gib Namen von Spieler {i + 1} ein: ")
for i in range(player_count)
]
def ask_letter():
while True:
letter = input("Gib einen Buchstaben ein: ").upper()
if len(letter) == 1:
return letter
print("Bitte genau einen Buchstaben eingeben!")
def play_round(players, word, word_display):
for player in players:
print(f"Aktueller Spieler: {player.name}.")
print("Suche: ", " ".join(word_display))
letter = ask_letter()
if letter not in word_display:
count = word.count(letter)
points = count * 10
print(
f"{letter!r} ist {count} mal enthalten,"
f" das gibt {points} Punkte."
)
player.points += points
word_display = [
letter if letter == word_letter else display_letter
for word_letter, display_letter in zip(
word, word_display
)
]
else:
print(
f"Der Buchstabe {letter!r} wurde bereits aufgedeckt."
)
return word_display
def get_winners(players):
get_key = attrgetter("points")
return next(
groupby(sorted(players, key=get_key, reverse=True), key=get_key)
)
def main():
player_names = ask_player_names(ask_player_count())
while True:
word = read_random_word(WORDS_FILENAME)
print("Es wurde ein zufälliges Wort ermittelt.")
word_display = ["_"] * len(word)
players = list(map(Player, player_names))
for _ in range(10):
word_display = play_round(players, word, word_display)
print(f"Das Wort lautet {word!r}.")
points, winners = get_winners(players)
winners_text = ", ".join(winner.name for winner in winners)
print(f"Gewinner mit {points} Punkten: {winners_text}.")
if __name__ == "__main__":
main()
Tag 6:
`split()` ist möglich, aber `partition()` wäre passender, aber letztlich sollte man so etwas nicht selber neu erfinden, sondern ein robustes Standardformat wie JSON verwenden. Die Beispiellösung kommt zum Beispiel nicht damit klar wenn jemand ein "=" im Spielernamen hat. Ich tippe da aber gerne mal "-=> BlackJack <=-" ein.
Nur die Teile die neu hinzugekommen sind:
Code: Alles auswählen
#!/usr/bin/env python3
import json
...
HIGHSCORE_FILENAME = "highscore.json"
def load_highscore(filename):
with open(filename, encoding="utf-8") as file:
return json.load(file)
def save_highscore(filename, name_to_points):
with open(filename, "w", encoding="utf-8") as file:
json.dump(name_to_points, file)
def update_highscore(name_to_points, players):
for player in players:
name_to_points[player.name] = max(
name_to_points.get(player.name, 0), player.points
)
def print_highscore(name_to_points):
if name_to_points:
print("Highscore")
print("---------")
for number, (points, name) in enumerate(
sorted(
((points, name) for name, points in name_to_points.items()),
reverse=True,
),
1,
):
print(f"{number:3d}. {points:4d} {name}")
...
def main():
name_to_points = load_highscore(HIGHSCORE_FILENAME)
print_highscore(name_to_points)
player_names = ask_player_names(ask_player_count())
while True:
...
update_highscore(name_to_points, players)
print_highscore(name_to_points)
save_highscore(HIGHSCORE_FILENAME, name_to_points)
if __name__ == "__main__":
main()
Tag 7:
Hier ändert sich mehr als man vielleicht auf den ersten Blick vermuten würde, denn wenn nicht mehr in 10 Runden jeder Spieler einmal dran kommt, sondern es ”unendlich” Runden gibt, und jeder Spieler solange dran kommt wie er richtig rät (oder löst), kann man die Schleife, die jeden Spieler durchgeht aus der `play_round()`-Funktion holen und die Funktion auf *einen* Spieler beschränken.
Da ich diesen nervigen Q/B-Zwischenschritt rausgenommen habe, geht R auch nicht — das unterscheide ich einfach danach ob der Benutzer einen einzelnen Buchstaben oder mehr eingegeben hat.
Die `play_round()` ist für meinen Geschmack auch zu gross geworden durch diese neue Möglichkeit, also gibt es eine `try_letter()`- und eine `try_word()`-Funktion.
Und ich habe endlich diese komische Sonderbehandlung rausgeworfen, dass man bei einem einzelnen Spieler keinen Namen eingeben muss. Das hätte eigentlich schon mit Einführung der Bestenliste passieren müssen.
Code: Alles auswählen
#!/usr/bin/env python3
import json
import random
from itertools import cycle, groupby, repeat
from operator import attrgetter
WORDS_FILENAME = "wortliste.txt"
HIGHSCORE_FILENAME = "highscore.json"
BLANK = "_"
class Player:
def __init__(self, name, points=0):
self.name = name
self.points = points
def load_highscore(filename):
with open(filename, encoding="utf-8") as file:
return json.load(file)
def save_highscore(filename, name_to_points):
with open(filename, "w", encoding="utf-8") as file:
json.dump(name_to_points, file)
def update_highscore(name_to_points, players):
for player in players:
name_to_points[player.name] = max(
name_to_points.get(player.name, 0), player.points
)
def print_highscore(name_to_points):
if name_to_points:
print("Highscore")
print("---------")
for number, (points, name) in enumerate(
sorted(
((points, name) for name, points in name_to_points.items()),
reverse=True,
),
1,
):
print(f"{number:3d}. {points:4d} {name}")
def read_random_word(filename):
with open(filename, encoding="utf-8") as lines:
return random.choice(list(lines)).strip().upper()
def ask_player_count():
while True:
player_count = int(input("Gib die Anzahl der Spieler ein: "))
if player_count >= 1:
return player_count
print("Die Anzahl muss ≥1 sein!")
def ask_player_names(player_count):
return [
input(f"Gib Namen von Spieler {i + 1} ein: ")
for i in range(player_count)
]
def ask_non_empty_text():
while True:
answer = (
input("Gib einen Buchstaben oder die Lösung ein: ").strip().upper()
)
if answer:
return answer
print("Bitte keine Leereingabe!")
def try_letter(player, word, word_display, letter):
if letter not in word_display:
letter_count = word.count(letter)
points = letter_count * 10
print(
f"{letter!r} ist {letter_count} mal enthalten,"
f" das gibt {points} Punkte."
)
player.points += points
success = points > 0
word_display = [
letter if letter == word_letter else display_letter
for word_letter, display_letter in zip(word, word_display)
]
else:
print(f"Der Buchstabe {letter!r} wurde bereits aufgedeckt.")
success = False
return success, word_display
def try_word(player, word, word_display, text):
success = text == word
if success:
points = 200
print(f"Korrekt, das gibt {points} Punkte.")
player.points += points
word_display = list(word)
else:
print("Leider falsch! Alle Punkte sind weg.")
player.points = 0
return success, word_display
def play_round(player, word, word_display):
print(f"Aktueller Spieler: {player.name}.")
while BLANK in word_display:
print("Suche: ", " ".join(word_display))
text = ask_non_empty_text()
success, word_display = (
try_letter(player, word, word_display, text)
if len(text) == 1
else try_word(player, word, word_display, text)
)
if not success:
break
return word_display
def get_winners(players):
get_key = attrgetter("points")
return next(
groupby(sorted(players, key=get_key, reverse=True), key=get_key)
)
def main():
name_to_points = load_highscore(HIGHSCORE_FILENAME)
print_highscore(name_to_points)
player_names = ask_player_names(ask_player_count())
while True:
word = read_random_word(WORDS_FILENAME)
print("Es wurde ein zufälliges Wort ermittelt.")
word_display = [BLANK] * len(word)
players = list(map(Player, player_names))
for player in (
repeat(players[0], 10) if len(players) == 1 else cycle(players)
):
if BLANK not in word_display:
break
word_display = play_round(player, word, word_display)
print(f"Das Wort lautet {word!r}.")
points, winners = get_winners(players)
winners_text = ", ".join(winner.name for winner in winners)
print(f"Gewinner mit {points} Punkten: {winners_text}.")
update_highscore(name_to_points, players)
print_highscore(name_to_points)
save_highscore(HIGHSCORE_FILENAME, name_to_points)
if __name__ == "__main__":
main()
Tag 8:
Och nöööö, schon wieder Regeländerungen einbauen. Wer weiss wie das mittlerweile bei tatsächlichen Anfängern aussieht, die keine Erfahrung haben wie man Code sinnvoll auf Funktionen aufteilt, und die vieles umständlich bis fragil zu Fuss programmieren, statt die passenden Syntaxkonstrukte und eingebauten Funktionen sowie Standardbibliotheksfunktionen zu verwenden. Ich sehe da einen riesenklumpen Code vor dem inneren Auge, der bereits einen Haufen fragwürdige Entwurfsentscheidungen enthält, auf den jetzt *noch mal* eine Schippe Regeländerungen und Erweiterungen drauf kommt, ohne das da irgend etwas sinnvoll refaktorisiert wird. Was bei Anfängern ja auch kein Vorwurf ist.
Das ist der Punkt wo ich persönlich den bisherigen Code wegwerfen und sauber neu anfangen würde. Und das auf jeden Fall mit Klassen, denn Das Wort und was davon aufgedeckt ist, gehört ja *sowas* von zusammen und hat auch Funktionen die darauf operieren, mindestens da macht eine Klasse einfach Sinn.
Ich weiss natürlich nicht wie Du an die Erstellung der Aufgaben herangegangen bist, aber es machte den Eindruck als wenn Du das selbst von ”vorne” entwickelt hast. Ich würde das anders herum machen. Erst das Ziel programmieren, und von dort aus dann schrittweise abspecken. Dann hat man IMHO besser im Griff welche Randbedingungen am Ende zu was führen. Das sich der Einspielermodus anders verhalten soll als der Mehrspielermodus, hätte man sich beispielsweise sparen sollen. IMHO. Das bringt da nur unnötige Komplexität rein wo man entscheiden muss auf welcher Ebene man die implementiert und wie. Und bei jeder Regeländerung/-erweiterung kann sich diese Entscheidung als ungünstig erweisen und man muss das alles noch mal überarbeiten, oder Anfänger fangen dann gerne mal an, an jeder Stelle zusätzlichen Code dazu zu basteln, um vorherige Entwurfsentscheidungen wieder auszubügeln.
Die Beispiellösungen enthalten mir persönlich auch schon fast zu viel Code in einer “Funktion“. Also erst einmal sollte sowieso alles in einer Funktion stecken was Programm ist. Und Funktionen kann man gerade als Anfänger eher nicht genug haben. Faustregel: Länge einer Funktion nicht länger als 50 Zeilen, lieber nur 25. Und in der Regel so 5 bis 10 lokale Namen. Und so maximal 5 bis 6 Argumente. Und da wird es bei `spieler_1`, `spieler_2`, `punkte_1`, `punkte_2`, `wort`, und `gefunden` schon knapp wenn man eine Funktion schreibt, die den gesamten Spielzustand benötigt. Dabei würde man normalerweise den Spielernamen und dessen Punkte jeweils zu einem Objekt zusammenfassen, und die beiden dann in einen Container stecken, und `wort` und `gefunden` zu einem Objekt zusammenfassen. Und schon ist dieser gesamte Spielzustand nur noch in zwei Werten, wobei die Anzahl der Spieler daran nichts ändert. Das bleiben auch bei 10 Spielern immer noch nur zwei Werte die übergeben werden müssen.