21 KiB
21 KiB
Code Annotator - Summary
Pending
- Funktion gibt Listen innerhalb einer Liste zurück, die das Feld darstellen. Die Listen sind mit Nullen befüllt, was bedeutet, dass alle Felder komplett leer sind.
def makeFeld():
feld = [[[0] for i in range(matrixgr)] for e in range(
matrixgr)] # Erzeugt ein mit Nullen befülltes Feld, was bedeutet,
# dass dort weder ein (zerstörtes) Schiff ist, noch ein
# fehlgeschlagener Angriff stattgefunden hat
return feld
- In die Liste Output werden die auszugebenden Elemente eingetragen, damit sie am Ende alle gleichzeitig nebeneinander ausgegeben werden können
output = [[gegnerFeld[matrixgr]], [
' ' + eigenFeld[matrixgr]]]
- Der Bezeichner für die Zeile, da Zeilen bei Schiffe Versenken nicht nummeriert sondern alphabetisch sind.
NZeile = 'A'
- Grundsätzliche Logik: Während man beim eigenen Spielfeld immer genau sehen möchte, was auf welchem Feld ist (Schiff/versenktes Schiff/Fehlschuss...), sollte man beim gegnerischen Spielfeld nicht alle Infos haben (also nicht wissen, ob auf einem noch nicht abgeschossenen Feld ein Schiff ist oder nicht). Immer wenn f == 0, bedeutet das, dass es sich gerade um das gegnerische Feld handelt, bei f == 1 hat man es mit dem gegnerischen zu tun.
for f in range(2):
- 0 bedeutet leeres Feld, also wird beim eigenen Spielfeld das Feld als leer angezeigt, beim gegnerischen Spielfeld ist eine Tilde.
if n == [0]:
if f == 1:
zeile += ' [ ] '
elif f == 0:
zeile += ' [ ~ ] '
- Eine 1 bedeutet, dass auf eine leeres Feld geschossen wurde. Das möchte man auf beiden Spielfeldern sehen.
elif n == [1]:
zeile += ' [ . ] '
- Die 2 steht für ungetroffene Schiffe. Seine eigenen möchte man sehen, die des Bots nicht.
elif n == [2]:
if f == 1:
zeile += ' [ ■ ] '
elif f == 0:
zeile += ' [ ~ ] '
- Die 3 steht für getroffene Felder, diese möchte man sowohl bei sich selbst als auch beim Bot sehen.
elif n == [3]:
zeile += ' [ X ] '
- Die Funktion ord () liefert den Zahlenwert, welchen ein Zeichen in der ASCII-Tabelle hat. a hat den Wert 97 und da wir genau so viele Buchstaben wollen wie es Zeilen gibt, wird dieser Check gemacht.
if ord(NZeile.lower()) - 97 < matrixgr:
output[f] += [NZeile + ' ' + zeile]
- In die Variable zeile wird geschrieben, was sich auf jedem Feld in einer Zeile befindet.
zeile = ''
- Die Funktion chr () liefert, das Zeichen, welches den mitgegeben Wert in der ASCII-Tabelle hat. Hierdurch wird der Buchstabe "hochgezählt".
NZeile = chr(ord(NZeile) + 1)
- Erst jetzt, nachdem durch alle Zeilen der beiden Spielfelder durchgegangen wurde, wird alles auf einmal ausgegeben. output [0] beinhaltet das gegnerische Spielfeld, output [1] das eigene.
for i in range(0, len(output[0])):
print(output[0][i], ' ' + output[1][i])
print("")
- Falls ein Feld abgeschossen wird, muss der Wert des Feldes um 1 erhöht werden. 0 --> 1, 2 --> 3. Dadurch, dass die 5 diese Funktion hat, muss nicht im Vorhinein geklärt werden, welchen Wert das gewählte Feld hat (und damit, ob es sich um einen Treffer handelt oder nicht), sondern das geschieht unabhängig davon hier in der Funktion für die Modifikation des Feldes.
if wert == 5:
wert = getFeld(spielerFeld, zeile,
spalte) + 1
- Setzt den Wert des mitgegebenen Feldes auf den mitgegebenen Wert.
spielerFeld[zeile][spalte][0] = wert
- Gibt den aktuellen Wert des mitgegeben Feldes aus.
return spielerFeld[zeile][spalte][0]
- Addiert alle Werte des mitgegeben Spielfelds.
def summeFeld(spielerFeld):
summe = 0
for i in spielerFeld:
for e in i:
for n in e:
summe += n
return summe
- Überprüft, ob im mitgegebenen Spielfeld 2er vorhanden sind. Wären keine mehr vorhanden, würde das bedeuten, dass keine ungetroffenen Schiffe mehr auf dem Spielfeld existieren und das Spiel somit beendet ist.
def zweicheck(spielerFeld):
for i in spielerFeld:
for e in i:
for n in e:
if n == 2:
return True
- Überprüft das mitgegebene Feld sowie die Felder "links", "rechts", "oben" und "unten" des Feldes auf 2er, also Schiffe. Falls das mitgegebene Feld schon ein Schiff beheimatet, kann direkt False zurückgegeben werden.
def checkUmfeld(spielerFeld, zeile, spalte):
if getFeld(spielerFeld, zeile,
spalte) == 2:
return False
- Überprüft, ob sich das Feld überhaupt auf dem Spielfeld befinden kann.
if matrixgr > zeile >= 0 and matrixgr > spalte >= 0:
- Prinzip: Wenn sich das benachbarte Feld nicht außerhalb des Spielfeldes befindet oder es keine 2 beinhaltet, ist es frei. zeile + 1 kann als "rechter Nachbar", spalte + 1 als "unterer Nachbar" usw. gewertet werden.
if zeile + 1 < matrixgr:
if getFeld(spielerFeld, zeile + 1, spalte) != 2:
summe += 1
else:
summe += 1
if spalte + 1 < matrixgr:
if getFeld(spielerFeld, zeile, spalte + 1) != 2:
summe += 1
else:
summe += 1
if zeile - 1 >= 0:
if getFeld(spielerFeld, zeile - 1, spalte) != 2:
summe += 1
else:
summe += 1
if spalte - 1 >= 0:
if getFeld(spielerFeld, zeile, spalte - 1) != 2:
summe += 1
else:
summe += 1
- Das Umfeld ist nur dann frei wenn sich oben, unten, links und rechts kein Schiff befindet, also summe exakt 4 ist.
if summe < 4:
return False
else:
return True
- Definiert, wie groß die Spielfelder sind, also 7x7. Hier ließe sich das ändern, trotzdem müssten an anderen Stellen auch noch Änderungen vorgenommen werden.
matrixgr = 7
- groesse bestimmt, wie groß das zu setzende Schiff sein soll. Am Ende muss die Summe aller Felder des Spielfelds also der vorherigen Spielfeldsumme + der doppelten Größe des neuen Feldes betragen, da ein ungetroffenes Schiff den Wert 2 hat.
while summeFeld(spielerFeld) != feldsumme + groesse * 2:
- Weil das Schiff komplett zufällig platziert wird, kann es sein, dass das zufällig gewählte Feld nicht geeignet ist und sich der Platzieralgorithmus "verrennt". Um das vorzubeugen, wird das Feld nach 25 Versuchen komplett neu besetzt. Da diese Funktion nur ganz am Anfang und auch innerhalb einer Schleife aufgerufen wird, ist die Anzahl an Schiffen am Ende trotzdem richtig.
if versuch < 25:
- spitze[0] ist eine zufällig gewählte Zeilen-, spitze[1] die zufällig generierte Spaltennummer.
spitze = [random.randint(0, matrixgr - 1),
random.randint(0, matrixgr - 1)]
- Falls das Schiff nur ein Feld groß sein soll und das Umfeld des zufällig gewählten Feldes frei ist, kann das Schiff direkt gesetzt werden.
if groesse == 1:
setFeld(spielerFeld, spitze[0], spitze[1], 2)
- Auf dem belegFeld wird die Schiffsgröße an den gleichen Koordinaten eingetragen, an denen beim spielerFeld der Wert des Feldes steht. Das ist wichtig um später zu bestimmen, ob das Schiff schon komplett abgeschossen wurde.
setFeld(belegFeld, spitze[0], spitze[1], 1)
- orientierung entscheidet, ob das Schiff horizontal (0) oder vertikal (1) ausgerichtet werden soll.
orientierung = random.randint(0, 1)
- Der ursprüngliche zufällige Wert der Spitze, da die Liste gleich bearbeitet wird.
orig = spitze[1]
- counterind bestimmt, an welcher Stelle in spitze gecountet werden soll.
counterind = 1
- Generelles Prinzip: Ab jetzt wird überprüft, ob das Schiff überhaupt in die ausgewählte Zeile oder Spalte hineinpasst. Ein Feld wird zu reihe hinzugefügt, falls sein Umfeld frei ist. Wenn nicht, wird reihe komplett zurückgesetzt.
while spitze[
counterind] < matrixgr:
if checkUmfeld(spielerFeld, spitze[0], spitze[1]):
reihe += [spitze[counterind]]
else:
reihe = []
summe = 0
- .count zählt, wie oft eine mitgegebene Zahl in einer Liste vorkommt. Wenn die ursprüngliche zufällig gewählte Spitze vorkommt und die Liste mindestens so lang ist wie das Schiff groß sein soll, kann weitergemacht werden.
if len(reihe) >= groesse and reihe.count(
orig) == 1:
- Die generierte Liste wird jetzt von hinten durchgegangen und erneut wird überprüft, ob das Umfeld frei ist.
for i in range(len(reihe) - 1,
len(reihe) - groesse - 1,
-1):
if orientierung == 0:
if checkUmfeld(spielerFeld, spitze[0], i):
summe += 1
else:
if checkUmfeld(spielerFeld, i, spitze[1]):
summe += 1
- Falls die Anzahl an (freien) Feldern in der Liste mit der Soll-Größe übereinstimmt, kann das Schiff endgültig gesetzt werden. Auch hier wird im belegFeld die Schiffgröße für später eingetragen.
if summe == groesse:
for i in range(len(reihe) - 1,
len(reihe) - groesse - 1, -1):
if orientierung == 0:
setFeld(spielerFeld, spitze[0], i, 2)
setFeld(belegFeld, spitze[0], i,
groesse)
else:
setFeld(spielerFeld, i, spitze[1], 2)
setFeld(belegFeld, i, spitze[1],
groesse)
- Hier kann die gewünschte Anzahl und Größe der Schiffe konfiguriert werden. Standard: 1x 4er-Schiff, 2x 3er-Schiff, 2x 2er-Schiff, 3x 1er-Schiff.
def fuellFeld(Feld, belegFeld):
for i in range(1):
setSchiff(Feld, 4, belegFeld)
for i in range(2):
setSchiff(Feld, 3, belegFeld)
for i in range(2):
setSchiff(Feld, 2, belegFeld)
for i in range(3):
setSchiff(Feld, 1, belegFeld)
- gr beinhaltet die größe des Schiffs, das gerade getroffen wurde, da das beim Setzen des Schiffes in belegFeld gespeichert wurde.
gr = getFeld(belegFeld, treffer[0], treffer[1])
- Falls das Schiff nur 1 groß war, werden alle umliegenden Felder als abgeschossen markiert, damit klar ist, dass dort kein Schiff mehr sein kann.
if gr == 1:
for i in range(-1, 2, 2):
if 0 <= treffer[0] + i < matrixgr:
setFeld(spielerFeld, treffer[0] + i, treffer[1], 1)
for i in range(-1, 2, 2):
if 0 <= treffer[1] + i < matrixgr:
setFeld(spielerFeld, treffer[0], treffer[1] + i, 1)
return True
- i bestimmt praktisch die Richtung und ist zuerst -1, dann +1. Es wird die Zeile und die Spalte durchgegangen, in denen sich das getroffene Feld befindet, bis ein Feld nicht mehr die gleiche größe hat wie das getroffene Schiff. So stehen am Ende der beiden for-Schleifen in der Liste schiff die Koordinaten aller Felder, auf denen sich das Schiff befindet/befand.
for i in range(-1, 2, 2):
if 0 <= treffer[0] + i < matrixgr:
if getFeld(belegFeld, treffer[0] + i, treffer[1]) == gr:
e = 1
while 0 <= treffer[0]+e*i<matrixgr and getFeld(belegFeld,
treffer[
0]+e*i,
treffer[
1])==gr:
schiff += [[treffer[0] + e * i, treffer[1]]]
e += 1
if len(schiff) == 1:
for i in range(-1, 2, 2):
if 0 <= treffer[1] + i < matrixgr:
if getFeld(belegFeld, treffer[0], treffer[1] + i) == gr:
e = 1
while 0 <= treffer[1]+e*i<matrixgr and getFeld(belegFeld,
treffer[0],
treffer[
1]+e*i)==gr:
schiff += [[treffer[0], treffer[1] + e * i]]
e += 1
- Falls ein Feld des Schiffes noch den Wert 2 besitzt, ist das Schiff noch nicht vollständig versenkt.
for i in schiff:
if getFeld(spielerFeld, i[0], i[1]) == 2:
return False
- Falls das Schiff vollständig versenkt ist, werden alle umliegenden Felder mit 1 befüllt, damit man diese nicht mehr abschießen kann und es klar ist, dass das Schiff komplett versenkt wurde.
for i in schiff:
for e in range(-1, 2, 2):
if 0 <= i[0] + e < matrixgr:
if getFeld(spielerFeld, i[0] + e, i[1]) != 3:
setFeld(spielerFeld, i[0] + e, i[1], 1)
for e in range(-1, 2, 2):
if 0 <= i[1] + e < matrixgr:
if getFeld(spielerFeld, i[0], i[1] + e) != 3:
setFeld(spielerFeld, i[0], i[1] + e, 1)
return True
- Da das Feld manuell eingegeben wird, muss sichergestellt werden, dass das Format korrekt ist. Ohne Leerzeichen muss die Eingabe 2 Zeichen lang sein, das erste Zeichen muss ein Buchstabe sein (isalpha) und der zweite eine Ziffer (isdigit).
feld = str(input("Wähle das Feld, das abgeschossen werden soll! ")).replace (" ", "")
if (len(feld) != 2) or (not feld[0].isalpha()) or (not feld[1].isdigit()):
- Der Return ist benötigt, damit nicht "im Hintergrund" das Spiel mit der falschen Eingabe weiterläuft und es zu einem Fehler kommt.
return 0
- Der eingegebene Buchstabe wird wieder durch ord () in eine Zahl umgewandelt und durch -97 zur Zeilennummer (mit 0 beginnend). Auch die Spaltennummer muss um 1 verringert werden, da die erste Spalte als "1" angezeigt wird, im Programm aber die Nummer 0 hat.
feld = [ord(feld[0].lower()) - 97, int(feld[
1]) - 1]
- Falls das abgeschossene Feld ein Treffer war, wird zuerst durch zweicheck () überprüft, ob noch eine 2 (also ein ungetroffenes Schiff) auf dem Spielfeld ist. Ist die Rückgabe hiervon False, heißt das logischerweise, dass das Spiel vorbei ist.
if wert + 1 == 3:
if not zweicheck(spielerFeld):
print("GRATULATION! Du hast gewonnen!!!")
return 0
- Falls das Spiel noch nicht vorbei ist, darf nach einem Treffer noch einmal geschossen werden. Zusätzlich wird das gewählte Feld wieder zurück in die eingegebene Form gewandelt und als erstes ausgegeben.
vers = ''
if schiffVersenkt(spielerFeld, belegSpFeld, feld):
print("\n\n\n")
printFelder()
vers = "Das ganze Schiffe wurde versenkt!"
print("[" + chr(feld[0] + 65) + str(feld[1] + 1) + "]", "TREFFER!",
vers, "\nEs darf direkt ein neues Feld gewählt werden!")
feldWahl(spielerFeld, belegSpFeld)
- Zunächst werden die vier Spielfeldlisten als globale Variabeln festgesetzt. Zwar werden sie in den meisten Funktionen als Argumente mitgegeben, manche greifen aber auch direkt auf sie zu, weshalb das nötig ist.
def Initialisiere():
global gegnerFeld, eigenFeld, belegEigFeld, belegGegFeld
- Zunächst wird das gegnerische Feld erzeugt und dann befüllt. Die while-Schleife dient dazu, dass auf jeden Fall 17 Felder durch Schiffe belegt sind, aber sich die erzeugende Funktion nicht "aufhängt" (s. fuellFeld).
gegnerFeld = makeFeld()
belegGegFeld = makeFeld()
fuellFeld(gegnerFeld, belegGegFeld)
while summeFeld(gegnerFeld) != 34:
gegnerFeld = makeFeld()
fuellFeld(gegnerFeld, belegGegFeld)
- In der Ausgabe steht oben immer, um welches Feld es sich handelt, das wird hier festgelegt.
gegnerFeld += ['Gegnerisches Feld:']
- Die Schleife stellt sicher, dass nur wenn J/j eingegeben wurde, das erzeugte Feld auch verwendet wird.
while eing.lower() != ('j'):
- Anders als bei der Konfirmations- und Feldabfrage kann man bei der Schwierigkeit nur eine (Komma-)Zahl eingeben. Da diese Eingabe direkt als Float statt als String behandelt wird, würde es bspw. bei Eingabe eines Zeichens zum Fehler kommen. Das wird mit try/except verhindert.
try:
diff = float(input(
"Bitte wähle die Schwierigkeit von 0-sehr einfach bis "
"10-sehr schwer! [0-10] "))
except:
print("Keine valide Eingabe!")
SchwierigkeitsWahl()
return 0
- Prinzip: Die Schwierigkeit wird durch Multiplikation mit 10 zu einer Zahl zwischen 0 und 100. Der "Bot würfelt" jeztzt eine Zahl zwischen -5 und 105. Ist der "Wurf des Bots" kleiner als die gewählte Schwierigkeit, gilt das als ein Treffer (Der Gegner kann also bei Schwierigkeit 0 nur treffen, wenn die Zufallszahl zwischen -5 und 0 liegt, bzw. bei einer Schiwerigkeit von 10 nur nicht treffen, wenn die Zuffalszahl zwischen 101 und 105 liegt.
def botwahl(spielerFeld, belegSpFeld, diff):
diff *= 10
wurf = random.randint(-5, 105)
feld = [random.randint(0, matrixgr-1), random.randint(0, matrixgr-1)]
treffer = False
- Das Auswählen des Feldes erfolgt komplett zufällig, es wird jedoch nur wirklich gespielt, falls es der Anforderung - ob es ein Treffer sein soll oder nicht - gerecht wird.
if diff >= wurf:
while getFeld(spielerFeld, feld[0], feld[1]) != 2:
feld = [random.randint(0, matrixgr - 1),
random.randint(0, matrixgr - 1)]
treffer = True
else:
while getFeld(spielerFeld, feld[0], feld[1]) != 0:
feld = [random.randint(0, matrixgr - 1),
random.randint(0, matrixgr - 1)]
- Da der Bot getroffen hat, wird das Spiel für eine Sekunde pausiert, damit nicht alles auf einmal in die Konsole ausgegeben wird und damit es so wirkt, als würde der Bot "überlegen". Außerdem muss die Schwierigkeit wieder durch 10 geteilt werden (da sie am Anfang damit multipliziert wurde). Zusätzlich wird sie aber noch etwas kleiner gemacht als am Anfang, damit gerade bei höheren Schwierigkeiten nicht allzu viele Treffer hintereinander gemacht werden.
time.sleep(1)
botwahl(spielerFeld, belegSpFeld, diff / 10.25)
- Diese Funktion ist das Grundgerüst des Spiels, in dem alles zusammenkommt. Zunächst wird die Zeit zum Start des Spiels gespeichert, damit später die Differenz ermittelt werden kann. Dann wird die Schwierigkeit eingegeben und danach die Felder aufgebaut.
def start():
startzeit = time.time()
level = SchwierigkeitsWahl()
Initialisiere()
printFelder()
- Die while True - Schleife endet nie, außer es kommt zu einem break. Das passiert nur, wenn in einem der beiden Felder keine 2er - also ungetroffene Schiffe - mehr vorhanden sind. außerdem wird nach dem eigenen Zug das Spiel für eine Sekunde pausiert, damit der Zug des Bots nicht direkt kommt.
while True:
feldWahl(gegnerFeld, belegGegFeld)
if not zweicheck(gegnerFeld):
break
time.sleep(1)
botwahl(eigenFeld, belegEigFeld, level)
if not zweicheck(eigenFeld):
break
- Wenn das Spiel vorbei ist, wird die Dauer ausgegeben und abgefragt, ob noch einmal gespielt werden soll. Wenn ja geht die Funktion nochmals von vorne los, wenn nein terminiert das Programm.
print("Das Spiel hat", int((time.time() - startzeit) // 60), "Minuten",
int(((time.time() - startzeit) % 60) // 1), "Sekunden gedauert.")
w = str(input("Noch eine Runde? [J: Ja/Irgendwas: Nein] "))
if w.lower() == 'j':
start()
- Außer den Imports ist das die einzige Zeile Code, die keine Funktionsdeklaration (bzw. in einer) ist. Sie löst nur den Start des Spiels aus.
start ()