initialise project
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
MYRUMMY CLI
|
||||
|
||||
Start:
|
||||
python main.py
|
||||
|
||||
Beim Start werden zwei Einstellungen abgefragt:
|
||||
- 13-1-Umlauf: Folgen dürfen über 13 hinweg weiterlaufen (z.B. 12,13,1)
|
||||
- Erstmeldung 30: Für die erste Meldung werden 30 Punkte gebraucht
|
||||
|
||||
Nach jedem beendeten Zug erscheint ein Übergabe-Bildschirm, damit der
|
||||
nächste Spieler die Karten des Vorgängers nicht sieht.
|
||||
|
||||
Farben sind absichtlich nur ANSI-Farben + Zahlen:
|
||||
Rot / Gelb / Grün / Blau
|
||||
Joker = J
|
||||
|
||||
Der Tisch darf während eines Zuges beliebig umgebaut werden. Erst `finish`
|
||||
prüft alle Gruppen. Dadurch muss der Editor keine gültigen Zwischenzustände
|
||||
erzwingen.
|
||||
|
||||
Befehle:
|
||||
m H G Handkarte H in Tischgruppe G
|
||||
m H new Handkarte H in neue Gruppe
|
||||
t G C Tischkarte C aus Gruppe G zurück in Hand
|
||||
n neue leere Gruppe
|
||||
a G1 G2 Gruppen verbinden
|
||||
s G POS Gruppe nach POS teilen
|
||||
d Karte ziehen
|
||||
u letzte Änderung rückgängig
|
||||
sort Hand sortieren
|
||||
finish Zug prüfen/beenden
|
||||
q beenden
|
||||
|
||||
Regeln der Implementierung:
|
||||
- 2 komplette Kartensätze
|
||||
- 4 Farben, Werte 1-13
|
||||
- 4 Joker
|
||||
- Satz: gleiche Zahl, unterschiedliche Farben, 3-4 Karten
|
||||
- Folge: gleiche Farbe, aufsteigend, 3+ Karten
|
||||
- Joker dürfen in Folgen Lücken füllen
|
||||
- Erstmeldung: mindestens 30 Punkte
|
||||
- Wer keine Handkarten mehr hat, gewinnt
|
||||
|
||||
Hinweis: Das ist eine bewusst einfache CLI-Version. Die Regeln für Sonderfälle
|
||||
wie Joker-Rücknahme, 1-13-Runden, Punktwertung nach Spielende usw. können später
|
||||
noch ergänzt werden.
|
||||
@@ -0,0 +1,75 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
RESET='\033[0m'
|
||||
COLORS={'r':'\033[31m','y':'\033[33m','g':'\033[32m','b':'\033[34m'}
|
||||
COLOR_ORDER={'r':0,'y':1,'g':2,'b':3}
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Card:
|
||||
color: str|None
|
||||
value: int|None
|
||||
joker: bool=False
|
||||
uid: int=0
|
||||
|
||||
def text(self):
|
||||
if self.joker:
|
||||
return f'{COLORS["r"]}J{RESET}'
|
||||
return f'{COLORS[self.color]}{self.value}{RESET}'
|
||||
|
||||
def plain(self):
|
||||
return 'J' if self.joker else f'{self.color}{self.value}'
|
||||
|
||||
|
||||
def deck():
|
||||
cards=[]
|
||||
uid=0
|
||||
for _ in range(2):
|
||||
for c in COLORS:
|
||||
for n in range(1,14):
|
||||
cards.append(Card(c,n,False,uid)); uid+=1
|
||||
for _ in range(2):
|
||||
cards.append(Card(None,None,True,uid)); uid+=1
|
||||
return cards
|
||||
|
||||
|
||||
def points(card):
|
||||
return 30 if card.joker else card.value
|
||||
|
||||
|
||||
def valid_group(cards):
|
||||
if not 3 <= len(cards) <= 4: return False
|
||||
normal=[c for c in cards if not c.joker]
|
||||
jok=len(cards)-len(normal)
|
||||
if not normal: return False
|
||||
return len({c.value for c in normal})==1 and len({c.color for c in normal})==len(normal) and len(normal)+jok>=3
|
||||
|
||||
|
||||
def valid_run(cards, wrap=False):
|
||||
if not 3 <= len(cards) <= 13: return False
|
||||
normal=sorted((c for c in cards if not c.joker), key=lambda x:x.value)
|
||||
jok=len(cards)-len(normal)
|
||||
if not normal or len({c.color for c in normal}) != 1: return False
|
||||
if len({c.value for c in normal}) != len(normal): return False
|
||||
if not wrap:
|
||||
gaps=sum(b.value-a.value-1 for a,b in zip(normal,normal[1:]))
|
||||
return gaps <= jok and normal[-1].value-normal[0].value+1+jok <= 13
|
||||
# 13-1-Umlauf: Werte liegen auf einem Kreis 1..13, die Folge ist ein
|
||||
# zusammenhängender Bogen. Alle Lücken außer der größten müssen durch
|
||||
# Joker gefüllt werden.
|
||||
gaps=[b.value-a.value-1 for a,b in zip(normal,normal[1:])]
|
||||
gaps.append(normal[0].value+13-normal[-1].value-1)
|
||||
return 13-len(normal)-jok <= max(gaps)
|
||||
|
||||
|
||||
def valid(cards, wrap=False):
|
||||
return valid_group(cards) or valid_run(cards, wrap)
|
||||
|
||||
|
||||
def kind(cards, wrap=False):
|
||||
if valid_run(cards, wrap): return 'Folge'
|
||||
if valid_group(cards): return 'Satz'
|
||||
return 'UNGÜLTIG'
|
||||
|
||||
|
||||
def sort_hand(cards):
|
||||
return sorted(cards,key=lambda c:(c.joker, COLOR_ORDER.get(c.color,99), c.value or 99))
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
import copy, random
|
||||
from cards import *
|
||||
|
||||
def yes_no(q, default=None):
|
||||
while True:
|
||||
a=input(q+' (j/n): ').strip().lower()
|
||||
if a in ('j','ja','y','yes'): return True
|
||||
if a in ('n','nein','no'): return False
|
||||
if not a and default is not None: return default
|
||||
print(' Bitte j oder n eingeben.')
|
||||
|
||||
class Player:
|
||||
def __init__(self,name):
|
||||
self.name=name
|
||||
self.hand=[]
|
||||
self.opened=False
|
||||
|
||||
class Game:
|
||||
def __init__(self):
|
||||
print('EINSTELLUNGEN')
|
||||
print(' ~~~~~~~~~~~~')
|
||||
self.wrap=yes_no(' 13-1-Umlauf erlauben (z.B. 12,13,1)')
|
||||
self.min30=yes_no(' Braucht die Erstmeldung 30 Punkte')
|
||||
self.players=[Player('Spieler 1'),Player('Spieler 2')]
|
||||
self.turn=0
|
||||
self.table=[]
|
||||
self.deck=deck(); random.shuffle(self.deck)
|
||||
self.history=[]
|
||||
self.drew=False
|
||||
self.handover=False
|
||||
for p in self.players:
|
||||
p.hand=sort_hand([self.deck.pop() for _ in range(14)])
|
||||
|
||||
@property
|
||||
def p(self): return self.players[self.turn]
|
||||
|
||||
def save(self):
|
||||
self.history.append(copy.deepcopy((self.players,self.table,self.deck,self.drew)))
|
||||
|
||||
def undo(self):
|
||||
if not self.history:
|
||||
print('Nichts zum Rückgängigmachen.'); return
|
||||
self.players,self.table,self.deck,self.drew=self.history.pop()
|
||||
|
||||
def clear(self): print('\033[2J\033[H',end='')
|
||||
|
||||
def show(self):
|
||||
self.clear()
|
||||
print('╔════════════════════════════════════════════════════╗')
|
||||
print('║ MYRUMMY ║')
|
||||
print('╚════════════════════════════════════════════════════╝')
|
||||
print(f' Am Zug: {self.p.name} Karten im Stapel: {len(self.deck)}')
|
||||
print(f' Regeln: 13-1-Umlauf: {"ja" if self.wrap else "nein"} Erstmeldung 30: {"ja" if self.min30 else "nein"}')
|
||||
print(f' Erstmeldung: {"ja" if self.p.opened else "nein"}')
|
||||
print('\nTISCH')
|
||||
if not self.table: print(' (leer)')
|
||||
for i,g in enumerate(self.table,1):
|
||||
print(f' {i:>2}: ' + ' '.join(f'{j}:{c.text()}' for j,c in enumerate(g,1)) + f' [{kind(g, self.wrap)}]')
|
||||
print('\nHAND')
|
||||
lines=[]; key=None
|
||||
for i,c in enumerate(self.p.hand,1):
|
||||
k='J' if c.joker else c.color
|
||||
if k!=key: lines.append([]); key=k
|
||||
lines[-1].append(f'{i}:{c.text()}')
|
||||
for l in lines: print(' '+ ' '.join(l))
|
||||
print('\nBEFEHLE')
|
||||
print(' m H G Handkarte H -> Gruppe G')
|
||||
print(' m H new Handkarte H -> neue Gruppe')
|
||||
print(' t G C Tischkarte C aus Gruppe G -> Hand')
|
||||
print(' n leere neue Gruppe')
|
||||
print(' a G1 G2 Gruppen verbinden')
|
||||
print(' s G POS Gruppe nach POS teilen')
|
||||
print(' d ziehen')
|
||||
print(' u undo')
|
||||
print(' sort Hand sortieren')
|
||||
print(' finish Zug prüfen/beenden')
|
||||
print(' q beenden')
|
||||
|
||||
def idx(self,x,maxi):
|
||||
n=int(x)
|
||||
if not 1<=n<=maxi: raise ValueError('Nummer außerhalb des gültigen Bereichs.')
|
||||
return n-1
|
||||
|
||||
def move_hand(self,h,g):
|
||||
hi=self.idx(h,len(self.p.hand)); self.save()
|
||||
card=self.p.hand.pop(hi)
|
||||
if g.lower()=='new': self.table.append([card])
|
||||
else: self.table[self.idx(g,len(self.table))].append(card)
|
||||
|
||||
def move_table(self,g,c):
|
||||
gi=self.idx(g,len(self.table)); ci=self.idx(c,len(self.table[gi])); self.save()
|
||||
self.p.hand.append(self.table[gi].pop(ci))
|
||||
if not self.table[gi]: self.table.pop(gi)
|
||||
self.p.hand=sort_hand(self.p.hand)
|
||||
|
||||
def merge(self,a,b):
|
||||
ai=self.idx(a,len(self.table)); bi=self.idx(b,len(self.table))
|
||||
if ai==bi: raise ValueError('Gleiche Gruppe.')
|
||||
self.save(); self.table[ai]+=self.table[bi]; self.table.pop(bi)
|
||||
|
||||
def split(self,g,pos):
|
||||
gi=self.idx(g,len(self.table)); pos=int(pos)
|
||||
if not 1<=pos<len(self.table[gi]): raise ValueError('Teilposition ungültig.')
|
||||
self.save(); x=self.table[gi]; self.table[gi]=x[:pos]; self.table.insert(gi+1,x[pos:])
|
||||
|
||||
def draw(self):
|
||||
if self.drew: raise ValueError('Du hast bereits gezogen.')
|
||||
if not self.deck: raise ValueError('Stapel leer.')
|
||||
self.save(); self.p.hand.append(self.deck.pop()); self.p.hand=sort_hand(self.p.hand); self.drew=True
|
||||
|
||||
def finish(self):
|
||||
if any(not g or not valid(g, self.wrap) for g in self.table):
|
||||
print('Zug ungültig: Es gibt eine ungültige Tischgruppe.')
|
||||
return False
|
||||
if not self.p.opened:
|
||||
if self.min30:
|
||||
total=sum(sum(points(c) for c in g) for g in self.table)
|
||||
if total<30:
|
||||
print(f'Erstmeldung braucht mindestens 30 Punkte. Aktuell: {total}')
|
||||
return False
|
||||
self.p.opened=True
|
||||
if not self.p.hand:
|
||||
print(f'\n{self.p.name} gewinnt!')
|
||||
return True
|
||||
self.turn=1-self.turn; self.drew=False; self.history=[]
|
||||
self.handover=True
|
||||
return False
|
||||
|
||||
def command(self,line):
|
||||
a=line.split()
|
||||
if not a:return False
|
||||
try:
|
||||
cmd=a[0].lower()
|
||||
if cmd=='m' and len(a)==3:self.move_hand(a[1],a[2])
|
||||
elif cmd=='t' and len(a)==3:self.move_table(a[1],a[2])
|
||||
elif cmd=='n':self.save();self.table.append([])
|
||||
elif cmd=='a' and len(a)==3:self.merge(a[1],a[2])
|
||||
elif cmd=='s' and len(a)==3:self.split(a[1],a[2])
|
||||
elif cmd=='d':self.draw()
|
||||
elif cmd=='u':self.undo()
|
||||
elif cmd=='sort':self.p.hand=sort_hand(self.p.hand)
|
||||
elif cmd=='finish':return self.finish()
|
||||
elif cmd in ('q','quit','exit'):raise SystemExit
|
||||
elif cmd=='show':pass
|
||||
else: raise ValueError('Unbekannter Befehl.')
|
||||
except (ValueError,IndexError) as e: print('Fehler:',e)
|
||||
input('\nENTER...')
|
||||
return False
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
self.show()
|
||||
if self.command(input('\n> ')): return
|
||||
if self.handover:
|
||||
self.handover=False
|
||||
self.clear()
|
||||
print(f'\n Zug beendet. Bitte gib den Computer an {self.p.name} weiter,')
|
||||
print(' damit der andere Spieler deine Karten nicht sehen kann.')
|
||||
input(' ENTER drücken, wenn du bereit bist...')
|
||||
@@ -0,0 +1,4 @@
|
||||
from game import Game
|
||||
|
||||
if __name__ == '__main__':
|
||||
Game().run()
|
||||
Reference in New Issue
Block a user