Skip to content

Instantly share code, notes, and snippets.

Created December 9, 2014 20:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/e97df77504507e036d10 to your computer and use it in GitHub Desktop.
Save anonymous/e97df77504507e036d10 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
WHITE = "#FFFFFF"
GRAY = "#CCCCCC"
RED = "#FF8888"
YELLOW = "#FFFF88"
GREEN = "#88FF88"
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from sys import exit
import colors
import models
import views
class TkController(object):
def __init__(self):
self.viewer = views.TkViewer()
# Events vom Viewer schnappen und an eigene Methoden weiterleiten
# Neues Spiel-GUI oeffnen
self.viewer.NewGameEvent.append(self.newGame)
# Leichter Schwierigkeitsgrad
self.viewer.EasyEvent.append(lambda: self.genFirstGrid(9, 9, 10))
# Mittlerer Schwierigkeitsgrad
self.viewer.NormalEvent.append(lambda: self.genFirstGrid(16, 16, 40))
# Schwerer Schwierigkeitsgrad
self.viewer.HardEvent.append(lambda: self.genFirstGrid(30, 16, 99))
# Eigener Schwierigkeitsgrad
self.viewer.CustomGameEvent.append(self.genCustomGrid)
# GUI fuer eigenen Schwierigkeitsgrad oeffnen
self.viewer.MyEvent.append(self.viewer.createUserSweeperGUI)
# Generell das Anklicken von Feldern
self.viewer.ClickOnFieldEvent.append(self.clickOnField) # y, x
# Generell das Rechtsklicken von Feldern
self.viewer.RightClickOnFieldEvent.append(self.rightClick) # y, x
# Programm beenden
self.viewer.QuitEvent.append(self.quit)
# Programminfo
self.viewer.InfoEvent.append(self.info)
def run(self):
"""Setzt alles in Gang^^."""
self.newGame()
self.viewer.run()
def quit(self):
"""Beendet das Programm."""
exit()
def info(self):
"""Benutzer hat auf Info geklickt."""
self.viewer.showInfo()
def newGame(self):
"""Startet ein neues Spiel."""
self.viewer.createDifficultyGUI()
def genFirstGrid(self, width, height, mines):
"""Grid erstellen."""
self.width = width
self.height = height
self.mines = mines
self.gamefield = None
self.viewer.createMineSweeperGUI(width, height)
def genCustomGrid(self, width, height, mines):
"""Eigenes Grid erstellen.
Es sind falsche Eingaben moeglich, deshalb braucht das
eine eigene Methode zur Fehlerpruefung.
"""
print(width, height, mines)
try:
width = int(width)
height = int(height)
mines = int(mines)
if mines+1 > width * height or width < 2 or width > 30 \
or height < 2 or height > 24:
raise ValueError
except ValueError:
self.viewer.showWrongCustom()
else:
self.genFirstGrid(width, height, mines)
def clickOnField(self, y, x):
"""Es wurde auf ein Spielfeld geklickt.
Erst hier wird das Modell erstellt. Dadurch wird verhindert,
dass der erste Klick vom Benutzer direkt eine Mine ausloest.
"""
if self.gamefield is None:
# Modell entsprechend generieren
self.gamefield = models.Gamefield(self.width, self.height)
self.gamefield.genMines(self.mines, noty=y, notx=x)
# Modell-Event schnappen: Feld geoeffnet
self.gamefield.OpenNonMineEvent.append(self.MDL_OpenNonMine)
# Modell-Event schnappen: Minenfeld geoeffnet
self.gamefield.OpenMineEvent.append(self.MDL_OpenMine)
# Modell-Event schnappen: Farbe geaendert
self.gamefield.ColorChangedEvent.append(self.MDL_ColorChanged)
# Modell-Event schnappen: Spiel gewonnen
self.gamefield.WinEvent.append(self.MDL_Winner)
# Weitergeben ans Modell:
self.gamefield.openField(y, x)
def rightClick(self, y, x):
"""Ein Rechtsklick wurde auf einem Feld ausgefuehrt."""
# Ein Rechtsklick, obwohl das Spielfeld noch nichtmal
# generiert wurde, haette keinen Sinn, da der Spieler
# beim ersten Klick sowieso keine Mine trifft.
if self.gamefield is not None:
self.gamefield.markField(y, x)
if self.gamefield.gamefield[y][x].color == colors.RED:
self.mines -= 1
else:
self.mines += 1
self.viewer.showStatusbar("Noch {} Minen übrig".format(self.mines))
def MDL_OpenNonMine(self, obj, y, x, value):
"""Modell meldet, ein Feld (keine Mine) wurde geoeffnet."""
if value != 0:
field = self.gamefield.gamefield[y][x]
self.viewer.changeText(y, x, value)
self.viewer.changeColorToWhite(y, x)
self.viewer.disableRightClick(y, x)
self.viewer.unbindLeftClick(y, x)
def MDL_ColorChanged(self, obj, y, x, color):
"""Modell meldet, ein Feld hat seine Farbe geaendert."""
# Wurde rot markiert? Dann Feld im Viewer auch
# rot markieren und dabei gleich deaktivieren.
if color == colors.RED:
self.viewer.changeColorToRed(y, x)
self.viewer.unbindLeftClick(y, x)
# Bei grauer Markierung dementsprechend anders vorgehen.
elif color == colors.GRAY:
self.viewer.changeColorToGray(y, x)
self.viewer.bindLeftClick(y, x)
def MDL_OpenMine(self, *args):
"""Modell meldet, eine Mine wurde geoeffnet."""
# Statusbar aktualisieren
self.viewer.showLoserBar()
# Alle Buttons deaktivieren
self.viewer.disableAllButtons()
# Alle noch nicht markierten Minen anzeigen
for i in self.gamefield.getMines():
self.viewer.changeColorToRed(i[0], i[1])
# Falsch markierte Minen gelb einfaerben
for i in self.gamefield.getFalsemarkedMines():
self.viewer.changeColorToYellow(i[0], i[1])
def MDL_Winner(self, *args):
"""Modell meldet, das Spiel wurde gewonnen."""
# Eingaben blockieren
self.viewer.disableAllButtons()
# Statusbar aktualisieren
self.viewer.showWinnerBar()
# Alle noch nicht markierten Minen anzeigen
for i in self.gamefield.getMines():
self.viewer.changeColorToRed(i[0], i[1])
# Falsch markierte Minen gelb einfaerben
for i in self.gamefield.getFalsemarkedMines():
self.viewer.changeColorToYellow(i[0], i[1])
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Der wohl billigste Eventhandler in Python^^.
class Event(list):
def notify(self, *args, **kwargs):
for listener in self:
listener(*args, **kwargs)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import views
import controls
if __name__ == "__main__":
controller = controls.TkController()
controller.run()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from random import randint
from sys import exit
import colors
import event
class Field(object):
id = 0
def __init__(self):
self.id = Field.id
Field.id += 1
# Die Nachbarfelder
self.onLeft = None
self.onRight = None
self.onUp = None
self.onDown = None
# Wert. Bei -1 = Mine, ansonsten die
# Anzahl der benachbarten Minen.
self.value = 0
# Felder sind am Anfang immer verschlossen...
self.closed = True
# Feldfarbe, in RGB-Notation
self.color = colors.GRAY
def __repr__(self):
return self.value
class Gamefield(object):
def __init__(self, width, height):
"""Generiert ein Feld und setzt alle Nachbarfelder."""
# Event-Handling:
# Event soll abgefeuert werden, wenn KEINE Mine geoeffnet wurde.
self.OpenNonMineEvent = event.Event() # Konvention: self, y, x, value
# Event soll abgefeuert werden, WENN eine Mine geoeffnet wurde.
self.OpenMineEvent = event.Event() # Konvention: self, y, x
# Event soll abgefeuert werden, wenn
# alle minenfreien Felder offen sind
self.WinEvent = event.Event() # Konvention: self
# Event soll bei Farbaenderung abgefeuert werden
self.ColorChangedEvent = event.Event() # Konvention: self, y, x, color
# Spielfeld generieren
self.width = width
self.height = height
self.gamefield = [
[Field() for _ in range(width)] for _ in range(height)
]
# Anzahl Minen speichern
self.minesCount = 0
# Anzahl Felder zum Oeffnen speichern
self.winCount = width * height
# Nachbarfelder zuweisen
for y, line in enumerate(self.gamefield):
for x, field in enumerate(line):
# Oben zuweisen
if y > 0:
field.onUp = self.gamefield[y-1][x]
# Unten zuweisen
if y < height-1:
field.onDown = self.gamefield[y+1][x]
# Links zuweisen
if x > 0:
field.onLeft = self.gamefield[y][x-1]
# Rechts zuweisen
if x < width-1:
field.onRight = self.gamefield[y][x+1]
def __repr__(self):
return self.gamefield
def setMine(self, y, x):
"""Setzt eine Mine auf das Feld.
Falls schon eine Mine auf dem Feld ist, wird False
zurueckgegeben, ansonsten True.
"""
# Fehlerpruefung: Es sollten immer mind. 10 freie
# Plaetze auf dem Spielfeld geben:
if self.minesCount-1 > self.width * self.height:
raise
field = self.gamefield[y][x]
# Schon eine Mine gesetzt? Dann gib "False" zurueck
if field.value == -1:
return False
# Mine setzen, Minen-Zaehler erhoehen u. Gewinn-Zaehler verringern
# (Bei Letzterem soll die Mine ja nicht wirklich aufgedeckt werden ;) )
field.value = -1
self.minesCount += 1
self.winCount -= 1
# Nachbarfelder informieren, dass eine Mine in der Naehe liegt:
for direction in (field.onLeft, field.onRight,
field.onUp, field.onDown):
if direction is not None and direction.value != -1:
direction.value += 1
# Diagonale Felder auch informieren:
for y_dir in (field.onUp, field.onDown):
if y_dir is not None:
for x_dir in (y_dir.onLeft, y_dir.onRight):
if x_dir is not None and x_dir.value != -1:
x_dir.value += 1
return True
def genMines(self, count, noty=-1, notx=-1):
"""Setzt eine festgelegte Anzahl Minen zufaellig ins Spielfeld.
noty und notx ist das Feld, in das keine Mine generiert wird
(ueblicherweise das Feld, welches der Spieler als Erstes anklickt).
Optional.
"""
# Zufallsposition generieren und Mine ggf. setzen
i = count
while i:
i -= 1
y = randint(0, self.height-1)
x = randint(0, self.width-1)
# Es wurde zufaellig die Position ermittelt, die nicht
# ermittelt werden duerfte? Dann keine Mine generieren
# und einen zusaetzlichen Schleifenlauf anfordern.
# TODO: Waer eigentlich sinnvoller, erst die Minen zu
# generieren und erst am Schluss zu pruefen, ob eine
# Mine an der fraglichen Position liegt und diese ein-
# fach zu entfernen...
if y == noty and x == notx:
i += 1
# False zurueckgegeben (= eine Mine existiert schon
# an dieser Position)? Dann wiederholen...
elif not self.setMine(y, x):
i += 1
def openField(self, y, x):
"""Oeffnet ein angegebenes Feld.
Falls Feld den Wert (Value) 0 hat, werden rekursiv auch alle
anderen Felder herum geoeffnet.
"""
field = self.gamefield[y][x]
# Feld bereits geoeffnet? Dann nichts tun; sonst oeffnen.
if not field.closed:
return None
field.closed = False
# Mine geoeffnet oder nicht? Je nachdem, das passende Event abfeuern:
if field.value == -1:
self.OpenMineEvent.notify(self, y, x)
else:
self.OpenNonMineEvent.notify(self, y, x, field.value)
# Ueberpruefe, ob gewonnen wurde:
self.winCount -= 1
if self.winCount == 0:
self.WinEvent.notify(self)
# Feld hat den Wert 0? Dann alle umliegenden Felder rekursiv oeffnen:
if field.value == 0:
# Nachbarfelder auch aufdecken:
for direction in (field.onLeft, field.onRight,
field.onUp, field.onDown):
if direction is not None:
y, x = self.__getPosByObject(direction)
self.openField(y, x)
# Diagonale Felder auch informieren:
for y_dir in (field.onUp, field.onDown):
if y_dir is not None:
for x_dir in (y_dir.onLeft, y_dir.onRight):
if x_dir is not None:
y, x = self.__getPosByObject(x_dir)
self.openField(y, x)
def __getPosByObject(self, field):
"""Gibt die Position des Objekts zurueck.
Rueckgabewert: Tupel (Y-Position, X-Position)
"""
id = field.id
for y, line in enumerate(self.gamefield):
for x, field in enumerate(line):
if field.id == id:
return (y, x)
def getPrintView(self):
"""Gibt das Feld zurueck.
Nur fuer TerminalViewer zu gebrauchen^^.
"""
ret = ""
for line in self.gamefield:
for field in line:
if field.closed:
ret += "#"
else:
if field.value == -1:
ret += "*"
else:
ret += str(field.value)
ret += "\n"
return ret
def DEBUG_getCheatView(self):
"""Gibt das komplette Feld zurueck.
DEBUG: Ignoriert, ob das Feld schon geoeffnet ist.
"""
ret = ""
for line in self.gamefield:
for field in line:
if field.value == -1:
ret += "*"
else:
ret += str(field.value)
ret += "\n"
return ret
def markField(self, y, x):
"""Markiert ein Feld.
Nach jedem Aufruf tauscht sich die Farbe
(bzw. "nur" der Variablenwert) zwischen
grau und rot."""
field = self.gamefield[y][x]
if field.color == colors.GRAY:
field.color = colors.RED
else:
field.color = colors.GRAY
self.ColorChangedEvent.notify(self, y, x, field.color)
def getMines(self):
"""Gibt alle Felder mit Minen zurueck.
Der Rueckgabewert ist eine Liste, die
pro Mine eine Y-/X-Liste enthaelt.
"""
ret = list()
for y, line in enumerate(self.gamefield):
for x, field in enumerate(line):
if field.value == -1:
ret.append(list())
ret[-1] = y, x
return ret
def getFalsemarkedMines(self):
"""Gibt alle roten Nicht-Minen-Felder zurueck.
Einfacher formuliert: Diese Funktion gibt saemtliche
Felder zurueck, die zwar rot markiert wurden,
aber keine Minen enthalten.
"""
ret = list()
for y, line in enumerate(self.gamefield):
for x, field in enumerate(line):
if field.value != -1 and field.color == colors.RED:
ret.append(list())
ret[-1] = y, x
return ret
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Fix fuer Python 2.x/3.x
try:
import Tkinter as tk
except ImportError:
import tkinter as tk
try:
import tkMessageBox as msgbox
except ImportError:
import tkinter.messagebox as msgbox
import colors
import event
class TkViewer(object):
def __init__(self):
"""Stellt Minesweeper in Tk-Fenstern dar."""
# Events bereitstellen, die ggf. ein Controller abfragt.
# Schwierigkeitsgradauswahl:
self.CustomGameEvent = event.Event() # Konvention: nix
self.NewGameEvent = event.Event() # Konvention: nix
self.QuitEvent = event.Event() # Konvention: nix
self.InfoEvent = event.Event() # Konvention: nix
self.EasyEvent = event.Event() # Konvention: nix
self.NormalEvent = event.Event() # Konvention: nix
self.HardEvent = event.Event() # Konvention: nix
self.MyEvent = event.Event() # Konvention: nix
# Wenn in ein Feld geklickt wird
self.ClickOnFieldEvent = event.Event() # Konvention: y, x
# Wenn auf ein Feld RECHTSgeklickt wird
self.RightClickOnFieldEvent = event.Event() # Konvention: y, x
# Root-Fenster
self.root = tk.Tk()
self.root.title("Another Minesweeper-Clone by Astorek86")
self.root.geometry("1000x700")
# Window-Layout; wird bei Bedarf geloescht und neu erstellt
self.window = tk.Frame(self.root)
self.window.pack(expand=True, fill=tk.BOTH)
# Menubar; aendert sich im laufenden Programm auch nicht mehr
# "Datei"-Menue
self.menubar = tk.Menu(self.root)
self.filemenu = tk.Menu(self.menubar, tearoff=0)
self.filemenu.add_command(label="Neues Spiel",
command=self.NewGameEvent.notify)
self.filemenu.add_separator()
self.filemenu.add_command(label="Programm beenden",
command=self.QuitEvent.notify)
self.menubar.add_cascade(label="Datei", menu=self.filemenu)
# "Hilfe"-Menue
self.infomenu = tk.Menu(self.menubar, tearoff=0)
self.infomenu.add_command(label="Hilfe", command=self.InfoEvent.notify)
self.menubar.add_cascade(label="Info", menu=self.infomenu)
self.root.config(menu=self.menubar)
# Statusbar
self.statusbar = tk.Label(self.root)
self.statusbar.pack(side=tk.BOTTOM, expand=False, anchor=tk.NW)
self.statusbar.configure(font=(None, 12), anchor=tk.W, justify=tk.LEFT)
def __createGrid(self, width, height):
"""Erstellt eine bestimmte Anzahl an Zellen.
Warnung: Vorher wird der Fensterinhalt komplett
geloescht!
"""
# Altes Fenster komplett loeschen & neu erstellen
self.window.destroy()
self.window = tk.Frame(self.root)
self.window.pack(expand=True, fill=tk.BOTH)
# Spalten generieren
self.columns = list()
for _ in range(width):
self.columns.append(tk.Frame(self.window))
self.columns[-1].pack(side=tk.LEFT, expand=True, fill=tk.BOTH)
# Buttons generieren
self.button = dict()
for x, column in enumerate(self.columns):
for y in range(height):
self.button[y, x] = tk.Button(column, height=True, width=True,
font=(None, 12))
# Nachtraegliches Aendern der Groesse verhindern
self.button[y, x].pack_propagate(0)
self.button[y, x].pack(side=tk.TOP, expand=True, fill=tk.BOTH)
# Statusbar-Text loeschen
self.statusbar.configure(text="")
def createDifficultyGUI(self):
"""Erstellt den Schwierigkeitsgrad-Auswahlbildschirm."""
self.__createGrid(2, 2)
self.button[0, 0].configure(text="Einfach\n(9x9 Felder, 10 Minen)",
command=self.EasyEvent.notify)
self.button[0, 1].configure(text="Normal\n(16x16 Felder, 40 Minen)",
command=self.NormalEvent.notify)
self.button[1, 0].configure(text="Schwierig\n(30x16 Felder, 99 Minen)",
command=self.HardEvent.notify)
self.button[1, 1].configure(text="Benutzerdefiniert",
command=self.MyEvent.notify)
def createUserSweeperGUI(self):
"""Erstellt einen Benutzerdefiniert-Schwierigkeitsgrad-Bildschirm."""
# Fenster neu erstellen
self.window.destroy()
self.window = tk.Frame(self.root)
self.window.pack(expand=True, fill=tk.BOTH)
# 2 Spalten generieren
left = tk.Frame(self.window)
left.pack(side=tk.LEFT, expand=True, fill=tk.BOTH)
right = tk.Frame(self.window)
right.pack(side=tk.LEFT, expand=True, fill=tk.BOTH)
# Text generieren
tmpdata = list()
for text in [
"Breite des Feldes (2-30):",
"Höhe des Feldes (2-24):",
"Anzahl der Minen:"
]:
j = tk.Label(left, font=(None, 10), anchor=tk.E, text=text)
j.pack(side=tk.TOP, expand=False, fill=tk.BOTH)
tmpdata.append(tk.Entry(right, font=(None, 12)))
tmpdata[-1].pack(side=tk.TOP, expand=False, fill=tk.BOTH)
w = tk.Button(
right, text="OK", bg=colors.GREEN,
command=lambda:
self.CustomGameEvent.notify(tmpdata[0].get(),
tmpdata[1].get(),
tmpdata[2].get())
)
w.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
def createMineSweeperGUI(self, width, height):
"""Hier wird das eigentliche Spielfeld erstellt.
Automatisch werden auch Links- und Rechtsklicks abgefragt und
saemtliche Farben auf grau geschaltet.
"""
self.__createGrid(width, height)
for button in self.button.items():
y = button[0][0]
x = button[0][1]
# Ueberall Linksklicks erlauben...
self.bindLeftClick(y, x)
# ...und Rechtsklicks auch:
self.button[y, x].bind(
'<Button-3>',
lambda event, y=y, x=x:
self.RightClickOnFieldEvent.notify(y, x)
)
# Farben schalten:
self.changeColorToGray(y, x)
def run(self):
self.root.mainloop()
def changeText(self, y, x, value):
"""Aendert den Text auf dem angegebenen Button."""
self.button[y, x].configure(text=value)
def changeColorToGray(self, y, x):
"""Aendert die Farbe eines Buttons auf Grau."""
self.button[y, x].configure(bg=colors.GRAY)
def changeColorToRed(self, y, x):
"""Aendert die Farbe eines Buttons auf Rot."""
self.button[y, x].configure(bg=colors.RED)
def changeColorToWhite(self, y, x):
"""Aendert die Farbe eines Buttons auf Weiss."""
self.button[y, x].configure(bg=colors.WHITE)
def changeColorToYellow(self, y, x):
"""Aendert die Farbe eines Buttons auf Gelb."""
self.button[y, x].configure(bg=colors.YELLOW)
def bindLeftClick(self, y, x):
"""Erlaubt Linksklicks (Standardmaessig erlaubt)."""
self.button[y, x].configure(
command=lambda y=y, x=x: self.ClickOnFieldEvent.notify(y, x)
)
self.button[y, x].config(state='normal')
def unbindLeftClick(self, y, x):
"""Verbietet Linksklicks."""
self.button[y, x].configure(command=None)
self.button[y, x].config(state='disabled')
def disableRightClick(self, y, x):
"""Deaktiviert Rechtsklicks auf das ausgewaehlte Feld."""
self.button[y, x].unbind('<Button-3>')
def disableAllButtons(self):
"""Deaktiviert saemtliche Buttons."""
for button in self.button.items():
y = button[0][0]
x = button[0][1]
self.unbindLeftClick(y, x)
self.disableRightClick(y, x)
def showLoserBar(self):
"""Zeigt eine -Du hast verloren-Meldung an."""
self.statusbar.configure(text="LEIDER VERLOREN! :(")
def showWinnerBar(self):
"""Zeigt eine -Du hast gewonnen-Meldung an."""
self.statusbar.configure(text="HERZLICHEN GLÜCKWUNSCH, ALLE FELDER"
+ "AUFGEDECKT! :)")
def showStatusbar(self, text):
"""Zeigt eine selbstdefinierte Meldung an."""
self.statusbar.configure(text=text)
def showInfo(self):
"""Zeigt eine Info an."""
msgbox.showinfo(
"Info",
"Erstellt von Astorek86.\n\nVielen Dank an die Python- und" +
"QBasic-\nCommunity für sämtliche erhaltenen Hilfen. :)"
)
def showWrongCustom(self):
"""Zeigt eine Fehlermeldung an."""
msgbox.showerror(
"Fehler",
"Eingaben konnten nicht korrekt verarbeitet werden, oder" +
"\ndie Anzahl der Minen übersteigt die Feldgröße."
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment