MauMau für blutige Anfänger

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
pudeldestodes
User
Beiträge: 65
Registriert: Samstag 9. Juni 2007, 23:45

Hi,

Ich hoffe, ich poste hier im richtigen Forum.
Das hier ist mein allererstes komplett selbst erstelltes (sprich ohne sinnloses c&p aus irgendwelchen Tutorials) Programm.
Es kann noch nicht viel, aber bevor ich weiter mache, würde ich ganz gerne mal eure Kritik dazu hören.
Für mich läuft es erstmal einfach nur, wobei vieles wahrscheinlich viel zu umständlich ist und ich bestimmt unbewusst einige hässliche Sachen gemacht habe.

Es handelt sich um ein ganz billiges MauMau, sprich die einzigen Regeln sind derzeit, dass die zu spielende Karte entweder
a) die selbe Farbe hat, wie die Karte, die gerade liegt
oder
b) vom selben Kartentyp ist, wie die Karte, die gerade liegt (z.B. Dame auf Dame).

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Competitive MauMau
#erstmal ohne KI

#Folgender Aufbau:
#kartendeck: das Kartendeck als Liste 
#kartenliste: enthält für jeden Spieler eine Liste seiner Karten
#kartenstock: die Karten, die nach dem Austeilen übrig bleiben (auch Talon)
#aktive_karte: die Karte, die gerade für alle Spieler zu sehen ist
#kartenablage: die Karten, die schon gespielt wurden außer der gerade aktiven
#				Karte. Ist der Kartenstock erschöpft, wird die Kartenablage ge-
#				mischt und ergibt den neuen Kartenstock

from random import shuffle

def generiere_kartendeck(x, joker=False, blaetter=1):
#x entspricht der Anzahl der Nummernkarten 0 <= x <= 8 (x=4 wäre zB 7,8,9,10)
#joker: False/True
# blaetter: ganze Zahl (2 für doppeltes Blatt etc.)
	global farben			#global, wegen spielzug() 
	global kartentypen 
	farben  = ['Ka', 'He', 'Pi', 'Kr'] 
	dummy = range(10, 10-x,-1)
	kartentypen = []
	for i in dummy:				#umwandeln der Int in Strings
		x = str(i)
		kartentypen.append(x)
	kartentypen = kartentypen + ['Bu', 'Da', 'Ko', 'As']
	if joker:
		kartentypen.append('Jo')
	else:
		pass	
	kartendeck = []
	for a in farben: 				
		for b in kartentypen: 
			kartendeck.append(a + b)
	kartendeck = kartendeck * blaetter
	return kartendeck

def austeilen(kartendeck, spieleranzahl, n):
#n Karten für jeden Spieler
#kartendeck ist die Liste aller Karten
	kartenliste = []
	for a in xrange(spieleranzahl):
		spielerkarten = kartendeck[a:spieleranzahl * n:spieleranzahl] 
		kartenliste.append(spielerkarten)
	
	for a in xrange(spieleranzahl):		#lösche spielerkarten aus kartendeck(=kartenstock)
		for b in kartenliste[a]:		
			del kartendeck[kartendeck.index(b)]
	kartenstock = kartendeck
	return kartenstock, kartenliste
	
def spielzug(spieler):					#wahrscheinlich besser ohne Fkt lösbar
	print 'Spieler', spieler, ':', kartenliste[spieler]
	spielereingabe = raw_input('Dein Spielzug: Entweder Karte spielen oder ' \
	'eine Karte (a)ufnehmen: ')
	if spielereingabe == 'a':						#Karte aufnehmen
		kartenliste[spieler].append(kartenstock[0])
		del kartenstock[0]
		zeige_aktive_karte()
		return aktive_karte
	elif spielereingabe in kartenliste[spieler]:	#Spielzugüberprüfung
		x = False								
		#Die ganze Aufteilung hier ist einfach "suboptimal"
		for farbe in farben:						# gleiche Farbe
			if (farbe in spielereingabe) and (farbe in aktive_karte):
				x = True
				break
			else:
				continue
		if x == False:
			for kartentyp in kartentypen: 			#gleicher Typ (König, Dame)
				if (kartentyp in spielereingabe) \
				and (kartentyp in aktive_karte):
					x = True
					break
				else:
					continue
		else:
			pass
		if x == True:								#Karte spielen
			entferne = kartenliste[spieler].index(spielereingabe)
			del kartenliste[spieler][entferne]
			kartenablage.append(aktive_karte)
			print  6 * '>', 'Es liegt:', spielereingabe
			return spielereingabe
		else:			
			print  6 * '>','Unzulässiger Spielzug.'
			spielzug(spieler)
	else:
		zeige_aktive_karte()
		print  6 * '>', 'Bitte eine Karte angeben, die sich in deinem ' \
		'Blatt befindet.'
		spielzug(spieler)	

def zeige_aktive_karte():
	print  6 * '>', 'Es liegt:', aktive_karte

#Spielanfang:
kartendeck = generiere_kartendeck(x=4, blaetter=2)
kartenablage = []
shuffle(kartendeck)

spieleranzahl = int(raw_input('Spieleranzahl (2-5)?'))
startkarten = int(raw_input('Wie viele Karten soll jeder Spieler zu Beginn '\
'bekommen?'))

kartenstock, kartenliste = austeilen(kartendeck, spieleranzahl, startkarten)
aktive_karte = kartenstock[0]
del kartenstock[0]
print 6 * '>', 'Es liegt:', aktive_karte

#Spielablauf
kontrolle = True
while kontrolle:
	for spieler in xrange(spieleranzahl):	
		aktive_karte = spielzug(spieler)
		if len(kartenstock) == 0:						#Kartenstock leer?
			kartenstock = kartenablage
			shuffle(kartenstock)
			kartenablage = []		
		elif len(kartenliste[spieler]) == 0:			#Siegbedingung
			print 'Spieler', spieler, 'hat gewonnen!'
			kontrolle = False
			break
		else:
			pass
print 'Ende'	
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Tipp 1:
Gib deinen Variablen und Funktionen Englische Namen. while kontrolle: sieht einfach schon ziemlich komisch aus ;)

Tip 2:
a, b, c etc. sind Normalerweise keine gute Dateinamen.
f für File sowie i für iterator/das was du in der Schleife hoch zählst ist aber ok.

Tip 3:

Code: Alles auswählen

else:
    pass
Kannst du weglassen.

Bevor ich mehr sagen kann muss ich erst mal nachlesen was MauMau überhaupt ist :)
N317V
User
Beiträge: 504
Registriert: Freitag 8. April 2005, 13:23
Wohnort: München

@veers: Mau Mau ist in der Schweiz wohl als "Tschau Sepp" bekannt.
http://de.wikipedia.org/wiki/Mau-Mau
Es gibt für alles eine rationale Erklärung.
Außerdem gibt es eine irrationale.

Wie man Fragen richtig stellt
BlackJack

Es gibt ein paar Namen die ein wenig aussagekräftiger sein könnten. `a`, `b` und `x` zum Beispiel.

``global`` sollte man vermeiden. Die Farben und Kartentypen kann man auch als Rückgabewerte aus `generiere_kartendeck()` heraus bekommen.

Das Hauptprogramm sollte man in eine Funktion stecken. Dann lässt sich die Datei als Modul importieren und man kann einzelne Funktionen testen oder wiederverwenden, ohne dass das Spiel abläuft. Und damit wird man auch gezwungen globale Variablen zu beseitigen.

Das Austeilen orientiert sich zu sehr am Kartenausteilen in der realen Welt und ist damit komplizierter als es sein müsste. Beim echten Kartenspiel werden die Karten reihum ausgegeben und nicht `n` Karten für jeden Spieler als "Block", weil das händische mischen nicht ganz so zufällig ist, bzw. derjenige der mischt versuchen kann, dass bestimmte Karten zusammen in einem "Block" bleiben. `random.schuffle()` ist gründlich und versucht auch nicht zu betrügen. ;-) Darum kann man die Karten in "Blöcken" vom Kartendeck nehmen und da die Anzahl bekannt ist, dann auch auf einen Schlag vom Kartendeck entfernen. Ungetestet:

Code: Alles auswählen

def austeilen(kartendeck, spieleranzahl, n): 
    """
    n Karten für jeden Spieler 
    kartendeck ist die Liste aller Karten
    """
    kartenliste = [kartendeck[i * n:i * n + n] for i in xrange(spieleranzahl)]
    del kartendeck[:n*spieleranzahl]
    return kartenliste
Wenn man das reihum verteilen der Karten in Python ausdrücken möchte, wäre folgendes direkter an die reale Handlung angelehnt und vielleicht auch ein wenig verständlicher:

Code: Alles auswählen

    kartenliste = [list() for dummy in xrange(spieleranzahl)]
    for dummy in xrange(n):
        for spieler_hand in kartenliste:
            spieler_hand.append(kartendeck.pop())
    return kartenliste
Die vorletzte Zeile in Deinem Quelltext ist überflüssig, die bindet nur einen anderen Namen an das Objekt, das vorher an `kartenliste` gebunden war. Ich gebe dieses Objekt gar nicht zurück, weil es in der Funktion ja bereits verändert wurde und das "aussen" sichtbar ist.

Der `spielzug()` ist etwas zu lang und hat eine Menge Verzweigungen. Von denen sind aber einige überflüssig. Wenn im ``else``-Zweig nichts gemacht wird (``pass``) oder nur etwas, dass sowieso passieren würde (``continue``), dann kann man ihn auch komplett weglassen.

Den `kartenstock` kann man besser als "stack" verwalten, dass heisst Karten vom Ende nehmen. Dann lässt sich die `pop()`-Methode benutzen und man muss das nicht in zwei Schritten selbst machen.

An die Funktion wird ein "Spieler" übergeben und sie greift dann immer damit als Index auf die globale `kartenliste` zu. Da "kennt" die Funktion mehr als sie müsste. Was sie braucht, ist eigentlich nur die Liste mit den Karten, die der aktuelle Spieler auf der Hand hat. Damit wird die Funktion kürzer, einfacher und man braucht einen "globalen" Namen weniger.

Die Abfrage ob die Karte zur aktiven Karte passt, ist in der Tat etwas umständlich. Steht ja im Kommentar. :-) Was die Sache verkompliziert ist die Tatsache, dass in der Karte zwei Informationen in einer Zeichenkette stecken, die sich nicht so einfach trennen lassen. Wenn man bei der Modellierung als Zeichenkette pro Karte bleiben möchte, könnte man zumindest dafür Sorgen, dass die beiden Informationen Farbe und Typ einfach "angesprochen" werden können. Wenn zum Beispiel beides immer genau zwei Zeichen lang ist, kann man die beiden Schleifen durch eine ``if``-Abfrage ersetzen:

Code: Alles auswählen

        if (spielereingabe[:2] == aktive_karte[:2]
            or spielereingabe[2:] == aktive_karte[2:]):
            spieler_hand.remove(spielereingabe)
            # ...
Besser wäre es die Informationen gar nicht erst zu vermischen und Karten als Objekt oder als Tupel (Farbe, Typ) zu modellieren.

Da für den Joker besondere Regeln gelten, würde ich die Überprüfung ob zwei Karten zusammen passen, in eine eigene Funktion auslagern.

Die Funktion bei einer Falscheingabe rekursiv erneut aufzurufen ist kein guter Stil. CPython optimiert Endrekursion nicht und hat auch eine Obergrenze bei der Rekursionstiefe. (Müsste es dann nicht Untergrenze heissen!? :-))

Die Kommentare direkt nach den ``def``-Zeilen würden sich übrigens als Doctsrings gut machen.
N317V
User
Beiträge: 504
Registriert: Freitag 8. April 2005, 13:23
Wohnort: München

Da ich selbst ein leidenschaftlicher Mau-Mau-Spieler bin, hab ich jetzt einfach mal ein paar Runden gezockt. Zum einen ist mir mal passiert, dass ich am Anfang zwei Karo Könige auf einer Hand hatte. Zum anderen schmiert mir das Ding manchmal ab. Fehlermeldung ist folgende:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:\Python25\Lib\site-packages\pythonwin\pywin\framework\scriptutils.py", line 307, in RunScript
    debugger.run(codeObject, __main__.__dict__, start_stepping=0)
  File "C:\Python25\Lib\site-packages\pythonwin\pywin\debugger\__init__.py", line 60, in run
    _GetCurrentDebugger().run(cmd, globals,locals, start_stepping)
  File "C:\Python25\Lib\site-packages\pythonwin\pywin\debugger\debugger.py", line 631, in run
    exec cmd in globals, locals
  File "C:\Dokumente und Einstellungen\dvvgdej\Desktop\MauMau.py", line 119, in <module>
    aktive_karte = spielzug(spieler)
  File "C:\Dokumente und Einstellungen\dvvgdej\Desktop\MauMau.py", line 68, in spielzug
    if (farbe in spielereingabe) and (farbe in aktive_karte):
TypeError: argument of type 'NoneType' is not iterable
Es gibt für alles eine rationale Erklärung.
Außerdem gibt es eine irrationale.

Wie man Fragen richtig stellt
BlackJack

Das mit den beiden gleichen Karten hat schon seine Richtigkeit: Es wird im Programm mit zwei Kartenspielen gespielt.
pudeldestodes
User
Beiträge: 65
Registriert: Samstag 9. Juni 2007, 23:45

Danke erstmal für eure Tipps. Bevor ich den nächsten großen Schritt in Angriff nehmen (die Belegung der Karten mit Aktionen), wird das alles eingebaut - jetzt scheint aber gerade so schön die Sonne ;)

@N317V: danke für den Hinweis. Ich hatte eigentlich gehofft diesen grandios dummen Fehler ausgemerzt zu haben. Wahrscheinlich gibt die spielzug()-Funktion irgendwo keinen Wert zurück und aktive_karte ist deswegen nicht belegt.

Ehrlich gesagt habe ich das ganze nicht so unglaublich ausgiebig getestet, dazu ist mir das ganze noch zu umständlich *g*
Zum Beispiel ist es dämlich dauernd die korrekte Groß-/Kleinschreibung des Strings zu benötigen, dass muss dringend ausgebessert werden. Dann brauche ich noch einen Sortieralgorithmus für das Spielerblatt.
Ich hatte mir außerdem überlegt, ob man auf der Konsole vielleicht nicht die Karten "zeichnen" lassen könnte. Das wäre cool, ich glaub das versuche ich vlt noch vor der Belegung der Karten mit Aktionen ^^
[veers]Gib deinen Variablen und Funktionen Englische Namen. while kontrolle: sieht einfach schon ziemlich komisch aus
Ach, die Ästhetik.... ;)
Nein, du hast schon recht, dann liest sich das schöner

.
[blackjack]Darum kann man die Karten in "Blöcken" vom Kartendeck nehmen und da die Anzahl bekannt ist, dann auch auf einen Schlag vom Kartendeck entfernen.
Das vereinfacht das ganze, in der Tat - da seh ich ja schon was auf mich zukommen, wenn ich mal irgendwann reale Prozesse abbilden muss. Hoffentlich mach ich dann nicht wieder einen ähnlichen Fehler *g*
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Wieso auf der Konsole?
Mit PyGame kannst du auch sehr einfach auf den Bildschirm Zeichnen (:
Wäre einen Blick Wert wenn die Konsole Variante "fertig" ist ;)
pudeldestodes
User
Beiträge: 65
Registriert: Samstag 9. Juni 2007, 23:45

@veers
Gut, dass schau ich mir dann mal in 1 Jahr an, wenn ich fertig bin ;)

Sodele:
So, jetzt hatte ich mal wieder ein wenig Zeit zu basteln.
Akueller Code hier. In Zeile 74 fehlt noch ein \.
Wenn das ernsthaft jemand testen will am besten mit 2 Spielern und so 5 Karten oder so ;)

Es gibt leider immer noch keine Sonderfunktionen für die einzelnen Karten.
Ich hoffe, ich habe alle Verbesserungsvorschläge umgesetzt:

1.) alles in Englisch. Naja, hoffentlich sind keine groben Übersetzungsfehler drin
2.) keine Globals. Aber naja, ich hab das Gefühl, dass ich die nur durch einen Kunstgriff "entfernt" habe. Jetzt übergebe ich
halt zum Beispiel an die players_turn-Funktion grandiose 6 Argumente. Ich befürchte fast, dass das eine Verschlimmbesserung ist?
3.) Austeilfunktion nach BlackJacks Vorschlag.
4.) unnötige pass/continue-Anweisungen entfernt
5.) pop-Methode für den Spielstock (jetzt talon)
6.) Karten werden jetzt als Tupel der Form (suit, rank, XX) repräsentiert. Das dritte Element ist ein separater "Identifier",
mit dem ich die Karten nachher einfach lexikalisch ordnen lassen kann. Er besteht aus zwei Ziffern. Ziffer 1 für Farbe (suit)
(0=Karo, 1=Herz, 2=Pik, 3=Kreuz), Ziffer 2 für die Art der Karte (7=0, 8=1....etc). Das erschien mir die einfachste Lösung (eigentlich war es die einzige, die mir eingefallen ist ;) ) um nachher die Karten in der Spielerhand ordnen zu können.
7.) Karten werden jetzt in der Konsole/Terminal "gezeichnet". Das ganze lässt sich am einfachsten komplett über den
Numblock steuern.

Ich hab jetzt mal noch nicht versucht die Überprüfung ob zwei Karten zusammenpassen auszulagern. Da muss ich mir erst nochmal
Gedanken dazu machen, wie ich die Funktionen der einzelnen Karte am besten implementieren will.

@BlackJack
Und schlussendlich ist mir nicht so ganz klar, was du damit meintest:
An die Funktion wird ein "Spieler" übergeben und sie greift dann immer damit als Index auf die globale `kartenliste` zu. Da "kennt" die Funktion mehr als sie müsste. Was sie braucht, ist eigentlich nur die Liste mit den Karten, die der aktuelle Spieler auf der Hand hat. Damit wird die Funktion kürzer, einfacher und man braucht einen "globalen" Namen weniger.


Also sollte ich der Funktion direkt die Kartenhand des Spielers card_list[player] als Argument übergeben?
Andererseits brauche ich den Spieler ja sowieso um die eine oder andere Nachricht zu auszugeben.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ich führe hier mal ein paar Dinge auf, die man einfacher machen könnte, aber ohne dass ich mir den Quellcode nun im Detail angeschaut habe:

Den Zugriff über Indizes kannst du dir an dieser, und auch einigen anderen, Stellen sparen. Aus

Code: Alles auswählen

card_set_tuple = []
for card in xrange(len(card_set)): 
    card_set_tuple.append(tuple(card_set[card]))
kannst du einfach folgendes machen:

Code: Alles auswählen

for card in card_set: 
    card_set_tuple.append(tuple(card]))
Es geht sogar noch simpler:

Code: Alles auswählen

card_set_tuple = map(tuple, card_set)

Code: Alles auswählen

lines_all_cards = ['' for i in xrange(6)]
Kannst du noch in

Code: Alles auswählen

lines_all_cards = ['']*6
vereinfachen. An der Stelle könnte man noch anbrigen, dass du einige Dinge in Konstanten auslagern solltest, wie hier die öminöse 6. Diese tritt an mehreren Stellen auf, aber die Bedeutung ist einfach nicht klar und noch viel schlimmer: Willst du den Wert ändern, musst du das an mehreren Stellen im Quellcode.


Dann kannst du noch

Code: Alles auswählen

if sort == True: 
    players_hand.sort(key=sort_function)
vereinfachen. Das "if sort == True:" wird an dieser Stelle durch "if sort:" besser ausgedrückt.

Da die Sortierfunktion nur an dieser Stelle benötigt wird, kanns du diese auch wie folgt angeben. Lediglich das Modul "operator" müsstest du noch importieren. Auch hier bietet sich wieder eine Konstante als Index an:

Code: Alles auswählen

players_hand.sort(key=operator.itemgetter(2))

Ganz am Ende solltest du noch das

Code: Alles auswählen

initialise_new_game()
in

Code: Alles auswählen

if __name__ == "__main__":
    initialise_new_game()
ändern. Sonst hast du eventuell später mal Probleme, wenn du dein Modul importierst. Mit der angegebenen Änderung wird die Funktion nur ausgeführt, wenn du dieses Modul als Hauptmodul startest.

Ach ja, Kommentare im Quellcode würden auch nicht schaden. Wenn du dir das Programm in sechs Monaten noch mal anschaust, wirst du sicher nichts mehr verstehen :-)
pudeldestodes
User
Beiträge: 65
Registriert: Samstag 9. Juni 2007, 23:45

Danke für deine Kritik und Tipps.

Gut, mehr Kommentare und mehr in Konstanten auslagern. Notiert :)

Die ominöse 6 kommt daher, dass die Karten aus sechs Linien dargestellt werden. Dazu lege ich eine Liste an und schreibe dort für die 6 Zeilen die Information zur Darstellung der Karten rein.

So, jetzt ist erstmal wieder Lernerei angesagt. Bis zum nächsten mal ;)
Antworten