Add --tosvg switch to render seating as SVG, plus test images and docs

Sitzplatzverteilung.alsSVG() (libs/Strukturdaten.py) draws tables as
white circles and people as colored circles inside their table's
circle, colored by group membership. Table radius is derived from
the number of seats so all person-circles fit without touching; the
table-to-table grid spacing is derived from the largest table radius
so tables never overlap regardless of their Koordinaten values.

libs/platz.py gains a --tosvg switch that writes
<indir>/Sitzplatzverteilung.svg alongside the existing text/CSV
output.

The three unittest scenarios now each write their rendering to
doc/images/, and doc/docu_tests.md explains the three configurations
and what each test actually verifies (including why the "split
groups land on true neighbor tables" property only holds for a
specifically chosen seed in the circle scenario, not in general).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Michael Stangl
2026-07-09 12:55:32 +02:00
parent 0232e54f37
commit 74e08f389c
9 changed files with 292 additions and 4 deletions
+18 -4
View File
@@ -29,9 +29,10 @@ artifacts to be aware of when touching this code:
Python 3 raises `TypeError` for that, which is what surfaced it. Fixed to call Python 3 raises `TypeError` for that, which is what surfaced it. Fixed to call
`G.Anzahl()`. `G.Anzahl()`.
There is no build system, test suite, or linter configured in this repo. `requirements.txt` There is no build system or linter configured in this repo. A `tests/` unittest suite
exists but is empty/commented — everything used is standard library — it's there only so exists (see below); `requirements.txt` is still empty/commented — everything used is
`bin/install_py.*` has something to install into the `.venv`. standard library — it's there only so `bin/install_py.*` has something to install into the
`.venv`.
## Running the program ## Running the program
@@ -81,6 +82,15 @@ GA cycle/penalty config:
resolved as `os.path.join(indir, ...)` in `platz.py` — they are no longer configured in resolved as `os.path.join(indir, ...)` in `platz.py` — they are no longer configured in
`platz.cfg`. `platz.cfg`.
Pass `--tosvg` to additionally write `<indir>/Sitzplatzverteilung.svg` via
`Sitzplatzverteilung.alsSVG()` (see below).
Run the test suite with `bin/run_tests.bat` / `bin/run_tests.sh` (or directly: `python -m
unittest discover -s tests -p "test_*.py" -v`). A stray global `PYTHONPATH` entry on this
machine points at an unrelated project that also defines a `tests` package — this shadows
`import tests` but does not affect `unittest discover`, which is what the run_tests
scripts use.
## Architecture ## Architecture
Two library modules under `libs/`, imported via `PYTHONPATH=$PLATZ_LIBS`: Two library modules under `libs/`, imported via `PYTHONPATH=$PLATZ_LIBS`:
@@ -115,7 +125,11 @@ Domain-agnostic and reusable in principle:
when no single table has enough free seats, recursively splitting further if needed, then when no single table has enough free seats, recursively splitting further if needed, then
calls `bewerten()` to score the arrangement per `Strafliste`. `mutieren()` picks the calls `bewerten()` to score the arrangement per `Strafliste`. `mutieren()` picks the
worst-scoring groups (`SchlechteGruppenTopX`), evicts them, and reseats them, hoping for a worst-scoring groups (`SchlechteGruppenTopX`), evicts them, and reseats them, hoping for a
better score. `speichern()` writes the two output files. better score. `speichern()` writes the two output files. `alsSVG()` renders the layout to
SVG: tables as white circles sized to fit all their seats' person-circles without
touching, people as colored circles (color = group, via `SVGGruppenFarben`) placed evenly
around the inside of their table's circle. The table-to-table grid spacing is derived
from the largest table radius so tables never overlap regardless of their `Koordinaten`.
- `XMLConfig` — parses `bestellung.xml` into `Personen`/`Gruppen`/VIP-group/VIP-seat-map, - `XMLConfig` — parses `bestellung.xml` into `Personen`/`Gruppen`/VIP-group/VIP-seat-map,
expanding each `<Person>` template by its sibling `<Anzahl>` into that many individual expanding each `<Person>` template by its sibling `<Anzahl>` into that many individual
bookings, and treating any `<Gruppe>` containing a `<Tisch>` tag as a VIP group pinned to bookings, and treating any `<Gruppe>` containing a `<Tisch>` tag as a VIP group pinned to
+4
View File
@@ -121,6 +121,10 @@ A run writes two files into the `--indir` directory:
- `TischePersonen.txt` — table → seated people, with venue/table/seat numbers. - `TischePersonen.txt` — table → seated people, with venue/table/seat numbers.
- `PersonenTische.csv` — person → table, sorted alphabetically by name. - `PersonenTische.csv` — person → table, sorted alphabetically by name.
Pass `--tosvg` to additionally write `Sitzplatzverteilung.svg` into the `--indir`
directory: tables are drawn as white circles, people as colored circles inside their
table's circle (color = group). See `doc/docu_tests.md` for example renderings.
## Further details ## Further details
See `CLAUDE.md` for a deeper architectural walkthrough of the GA framework and domain See `CLAUDE.md` for a deeper architectural walkthrough of the GA framework and domain
+84
View File
@@ -0,0 +1,84 @@
# Dokumentation der Unittest-Szenarien
Die drei Testfaelle in `tests/test_sitzplatzverteilung.py` pruefen die Platzierungs- und
Trennungslogik von `Sitzplatzverteilung` (in `libs/Strukturdaten.py`) anhand konkreter,
handgebauter Tisch-/Gruppenkonfigurationen. Jeder Test schreibt zusaetzlich eine
SVG-Visualisierung nach `doc/images/` (erzeugt mit `Sitzplatzverteilung.alsSVG()`, das auch
ueber den `--tosvg`-Schalter von `bin/platz.bat`/`bin/platz.sh` erreichbar ist). Tische
werden darin als weisse Kreise dargestellt, Personen als farbige Kreise innerhalb ihres
Tischkreises; alle Personen derselben Gruppe haben dieselbe Farbe.
## Szenario 1: Drei Tische in einer Kette, drei Paare
`TestDreiTischeKettePaare` in `tests/test_sitzplatzverteilung.py`
- Drei Tische `T1-T2-T3` (Kette: T1 nur mit T2 benachbart, T3 nur mit T2 benachbart), je
2 Plaetze.
- Drei Gruppen zu je 2 Personen (Paare).
Da jeder Tisch exakt so viele Plaetze hat wie eine Gruppe Personen, passt jedes Paar
komplett an einen Tisch. Der Test prueft, dass keine Gruppe getrennt wird
(`GruppenTeilungGet(G) == 0` fuer alle drei Gruppen) und dass die optimale Wertigkeit
(`SV.value == 0`, keine Strafpunkte) erreicht wird.
![Drei Tische in einer Kette, drei Paare](images/drei_tische_kette_paare.svg)
## Szenario 2: Vier Tische in einer Linie, zwei Vierergruppen
`TestVierTischeLinieViergruppen` in `tests/test_sitzplatzverteilung.py`
- Vier Tische `T1-T2-T3-T4` in einer Linie (T1↔T2↔T3↔T4), je 2 Plaetze.
- Zwei Gruppen zu je 4 Personen.
Da kein Tisch 4 Plaetze hat, muss jede Vierergruppe auf zwei Tische aufgeteilt werden. Der
Test prueft, dass jede Gruppe genau einmal geteilt wird (`GruppenTeilungGet(G) == 1`) und
dass die beiden Teiltische pro Gruppe ein zusammenhaengendes Nachbarpaar bilden — entweder
`{T1,T2}` oder `{T3,T4}}`, nie ueber die mittlere Naht zwischen T2 und T3 hinweg vermischt.
Ausserdem sind am Ende alle vier Tische voll besetzt.
![Vier Tische in einer Linie, zwei Vierergruppen](images/vier_tische_linie_viergruppen.svg)
## Szenario 3: Sechs Tische im Kreis, vier Dreiergruppen
`TestSechsTischeKreisDreiergruppen` in `tests/test_sitzplatzverteilung.py`
- Sechs Tische `T1..T6`, im Kreis angeordnet (jeder Tisch hat genau zwei Nachbarn: z.B. T1
ist mit T2 und T6 benachbart), je 2 Plaetze.
- Vier Gruppen zu je 3 Personen (12 Personen auf 6×2 = 12 Plaetzen — exakt ausgelastet).
Da kein Tisch 3 Plaetze hat, muss jede Dreiergruppe auf zwei Tische aufgeteilt werden
(2+1). Der Test prueft zwei Arten von Eigenschaften:
- **Immer gueltig, unabhaengig vom Zufalls-Seed:** Bei exakt ausgelasteten 12 Plaetzen fuer
12 Personen bleibt keine andere Aufteilung uebrig — jede Gruppe wird also garantiert
genau einmal geteilt (`GruppenTeilungGet(G) == 1`), und am Ende sind alle sechs Tische
voll besetzt.
- **Nur mit dem hier fest gewaehlten Seed zusaetzlich beobachtet:** Die beiden Teiltische
jeder Gruppe sind tatsaechlich echte Nachbarn im Kreis. Das ist keine Garantie des
Algorithmus fuer jeden beliebigen Seed — bei sehr knapper Auslastung kann der
GA-Platzierungsfallback (`Sitzplatzverteilung.GruppeSetzen`, wenn `NachbarTische` keine
passenden Nachbarn mehr findet) auch nicht benachbarte Tische fuer die beiden Teile einer
Gruppe waehlen. Mit einem grossen Stichprobentest ueber viele Seeds trat das in gut zwei
Dritteln der Faelle auf; der im Test verwendete Seed wurde bewusst so gewaehlt, dass
zusaetzlich die Nachbarschaftseigenschaft zutrifft, um sie hier sichtbar zu machen.
Die `Koordinaten` der sechs Tische in diesem Test sind bewusst auf einem Kreis im
mathematischen Sinn angeordnet (Winkel gleichmaessig verteilt), damit auch die
SVG-Visualisierung die Kreisform erkennen laesst — die tatsaechliche Nachbarschaft, die der
Algorithmus fuer die Platzierung nutzt, kommt aber ausschliesslich aus der
`Nachbarliste`/`Nachbarn`-Angabe jedes Tisches, nicht aus den Koordinaten.
![Sechs Tische im Kreis, vier Dreiergruppen](images/sechs_tische_kreis_dreiergruppen.svg)
## Bilder neu erzeugen
Die drei SVGs in `doc/images/` werden beim Ausfuehren der Testsuite automatisch neu
geschrieben:
```bash
bin/run_tests.sh
```
```bat
bin\run_tests.bat
```
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="251.2" height="90.4" viewBox="0 0 251.2 90.4">
<rect width="100%" height="100%" fill="white"/>
<circle cx="45.2" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="45.2" y="8" text-anchor="middle" font-size="10" fill="black">Tisch 1</text>
<circle cx="45.2" cy="32" r="12" fill="#e6194b" stroke="black" stroke-width="1"/>
<circle cx="45.2" cy="58.4" r="12" fill="#e6194b" stroke="black" stroke-width="1"/>
<circle cx="125.6" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="125.6" y="8" text-anchor="middle" font-size="10" fill="black">Tisch 2</text>
<circle cx="125.6" cy="32" r="12" fill="#3cb44b" stroke="black" stroke-width="1"/>
<circle cx="125.6" cy="58.4" r="12" fill="#3cb44b" stroke="black" stroke-width="1"/>
<circle cx="206" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="206" y="8" text-anchor="middle" font-size="10" fill="black">Tisch 3</text>
<circle cx="206" cy="32" r="12" fill="#4363d8" stroke="black" stroke-width="1"/>
<circle cx="206" cy="58.4" r="12" fill="#4363d8" stroke="black" stroke-width="1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="251.2" height="412" viewBox="0 0 251.2 412">
<rect width="100%" height="100%" fill="white"/>
<circle cx="125.6" cy="366.8" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="125.6" y="329.6" text-anchor="middle" font-size="10" fill="black">Tisch 1</text>
<circle cx="125.6" cy="353.6" r="12" fill="#e6194b" stroke="black" stroke-width="1"/>
<circle cx="125.6" cy="380" r="12" fill="#3cb44b" stroke="black" stroke-width="1"/>
<circle cx="206" cy="286.4" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="206" y="249.2" text-anchor="middle" font-size="10" fill="black">Tisch 2</text>
<circle cx="206" cy="273.2" r="12" fill="#e6194b" stroke="black" stroke-width="1"/>
<circle cx="206" cy="299.6" r="12" fill="#e6194b" stroke="black" stroke-width="1"/>
<circle cx="206" cy="125.6" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="206" y="88.4" text-anchor="middle" font-size="10" fill="black">Tisch 3</text>
<circle cx="206" cy="112.4" r="12" fill="#4363d8" stroke="black" stroke-width="1"/>
<circle cx="206" cy="138.8" r="12" fill="#4363d8" stroke="black" stroke-width="1"/>
<circle cx="125.6" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="125.6" y="8" text-anchor="middle" font-size="10" fill="black">Tisch 4</text>
<circle cx="125.6" cy="32" r="12" fill="#4363d8" stroke="black" stroke-width="1"/>
<circle cx="125.6" cy="58.4" r="12" fill="#f58231" stroke="black" stroke-width="1"/>
<circle cx="45.2" cy="125.6" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="45.2" y="88.4" text-anchor="middle" font-size="10" fill="black">Tisch 5</text>
<circle cx="45.2" cy="112.4" r="12" fill="#f58231" stroke="black" stroke-width="1"/>
<circle cx="45.2" cy="138.8" r="12" fill="#f58231" stroke="black" stroke-width="1"/>
<circle cx="45.2" cy="286.4" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="45.2" y="249.2" text-anchor="middle" font-size="10" fill="black">Tisch 6</text>
<circle cx="45.2" cy="273.2" r="12" fill="#3cb44b" stroke="black" stroke-width="1"/>
<circle cx="45.2" cy="299.6" r="12" fill="#3cb44b" stroke="black" stroke-width="1"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="331.6" height="90.4" viewBox="0 0 331.6 90.4">
<rect width="100%" height="100%" fill="white"/>
<circle cx="45.2" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="45.2" y="8" text-anchor="middle" font-size="10" fill="black">Tisch 1</text>
<circle cx="45.2" cy="32" r="12" fill="#e6194b" stroke="black" stroke-width="1"/>
<circle cx="45.2" cy="58.4" r="12" fill="#e6194b" stroke="black" stroke-width="1"/>
<circle cx="125.6" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="125.6" y="8" text-anchor="middle" font-size="10" fill="black">Tisch 2</text>
<circle cx="125.6" cy="32" r="12" fill="#e6194b" stroke="black" stroke-width="1"/>
<circle cx="125.6" cy="58.4" r="12" fill="#e6194b" stroke="black" stroke-width="1"/>
<circle cx="206" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="206" y="8" text-anchor="middle" font-size="10" fill="black">Tisch 3</text>
<circle cx="206" cy="32" r="12" fill="#3cb44b" stroke="black" stroke-width="1"/>
<circle cx="206" cy="58.4" r="12" fill="#3cb44b" stroke="black" stroke-width="1"/>
<circle cx="286.4" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
<text x="286.4" y="8" text-anchor="middle" font-size="10" fill="black">Tisch 4</text>
<circle cx="286.4" cy="32" r="12" fill="#3cb44b" stroke="black" stroke-width="1"/>
<circle cx="286.4" cy="58.4" r="12" fill="#3cb44b" stroke="black" stroke-width="1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+110
View File
@@ -13,6 +13,7 @@ from itertools import chain
from random import * from random import *
import configparser, os import configparser, os
import copy import copy
import math
from functools import total_ordering from functools import total_ordering
from xml.dom import minidom from xml.dom import minidom
@@ -532,6 +533,115 @@ class Sitzplatzverteilung:
fout.writelines( Liste ) fout.writelines( Liste )
fout.close() fout.close()
# Farbpalette fuer die Gruppen-Einfaerbung im SVG (wird zyklisch
# wiederverwendet, falls es mehr Gruppen als Farben gibt)
SVGGruppenFarben = [
'#e6194b', '#3cb44b', '#4363d8', '#f58231', '#911eb4',
'#42d4f4', '#f032e6', '#bfef45', '#fabed4', '#469990',
'#dcbeff', '#9a6324', '#800000', '#aaffc3', '#000075' ]
def alsSVG(self, OutputSVG, PersonenRadius=12, Puffer=10):
"""Zeichnet die Sitzplatzverteilung als SVG:
Tische werden als weisse Kreise dargestellt, Personen als
farbige Kreise (Farbe = Gruppe) innerhalb ihres Tischkreises.
Das Koordinatenraster der Tische wird so skaliert, dass alle
Personenkreise in ihrem Tischkreis Platz haben und sich die
Tischkreise untereinander nicht ueberschneiden.
"""
# Tischradius je Tisch berechnen: gross genug, damit alle
# Personenkreise (auf einem inneren Kreis gleichmaessig verteilt)
# nebeneinander Platz haben, ohne sich zu beruehren. Der Faktor
# 1.1 auf den rechnerischen Mindestabstand ist ein kleiner
# Sicherheitsabstand gegen Rundungsfehler und damit sich die
# Kreise auch optisch sichtbar nicht beruehren.
def SitzRadiusVon(Plaetze):
if Plaetze <= 1:
return 0.0
return 1.1 * PersonenRadius / math.sin(math.pi / Plaetze)
def TischRadius(Plaetze):
return SitzRadiusVon(Plaetze) + PersonenRadius + Puffer
TidZuRadius = {}
GroessterRadius = 0
for T in self.Tische:
r = TischRadius( T.Plaetze )
TidZuRadius[T.Id] = r
if r > GroessterRadius:
GroessterRadius = r
# Rasterabstand der Tisch-Koordinaten: doppelter groesster
# Tischradius (plus Puffer) verhindert Ueberschneidungen
# unabhaengig davon, welche Tische tatsaechlich benachbart sind
Rasterabstand = 2 * GroessterRadius + Puffer
Xs = [ T.X_Koordinate for T in self.Tische ]
Ys = [ T.Y_Koordinate for T in self.Tische ]
MinX, MinY = min(Xs), min(Ys)
MaxX, MaxY = max(Xs), max(Ys)
Rand = GroessterRadius + Puffer
Breite = (MaxX - MinX) * Rasterabstand + 2 * Rand
Hoehe = (MaxY - MinY) * Rasterabstand + 2 * Rand
def TischMitte(T):
x = (T.X_Koordinate - MinX) * Rasterabstand + Rand
y = (T.Y_Koordinate - MinY) * Rasterabstand + Rand
return (x, y)
def GruppeVonPerson(P):
try:
return self.Gruppen.GruppeVonPid(P.Id)
except KeyError:
return None
GidZuFarbe = {}
def FarbeVonGruppe(G):
if G is None:
return '#888888'
if G.Id not in GidZuFarbe:
Index = len(GidZuFarbe) % len(self.SVGGruppenFarben)
GidZuFarbe[G.Id] = self.SVGGruppenFarben[Index]
return GidZuFarbe[G.Id]
Teile = []
Teile.append( '<?xml version="1.0" encoding="UTF-8"?>\n' )
Teile.append(
'<svg xmlns="http://www.w3.org/2000/svg" width="%g" height="%g" '
'viewBox="0 0 %g %g">\n' % (Breite, Hoehe, Breite, Hoehe) )
Teile.append( '<rect width="100%" height="100%" fill="white"/>\n' )
for T in self.Tische:
Mx, My = TischMitte(T)
TR = TidZuRadius[T.Id]
Teile.append(
'<circle cx="%g" cy="%g" r="%g" fill="white" '
'stroke="black" stroke-width="1.5"/>\n' % (Mx, My, TR) )
Teile.append(
'<text x="%g" y="%g" text-anchor="middle" '
'font-size="10" fill="black">Tisch %s</text>\n'
% (Mx, My - TR - 2, T.Id) )
Plaetze = list(T)
n = len(Plaetze)
SitzRadius = SitzRadiusVon( T.Plaetze )
for i, P in enumerate(Plaetze):
Winkel = 2 * math.pi * i / max(n, 1) - math.pi / 2
Px = Mx + SitzRadius * math.cos(Winkel)
Py = My + SitzRadius * math.sin(Winkel)
Farbe = FarbeVonGruppe( GruppeVonPerson(P) )
Teile.append(
'<circle cx="%g" cy="%g" r="%g" fill="%s" '
'stroke="black" stroke-width="1"/>\n'
% (Px, Py, PersonenRadius, Farbe) )
Teile.append( '</svg>\n' )
fout = open( OutputSVG, 'w' )
fout.writelines( Teile )
fout.close()
def mutieren(self): def mutieren(self):
"""Aendert die vorhandene Sitzplatzverteilung hoffentlich zum Besseren""" """Aendert die vorhandene Sitzplatzverteilung hoffentlich zum Besseren"""
[IdsSchlechteGruppen, Wertigkeiten] = self.SchlechteGruppenTopX(Anzahl=self.MutationsGroesse) [IdsSchlechteGruppen, Wertigkeiten] = self.SchlechteGruppenTopX(Anzahl=self.MutationsGroesse)
+7
View File
@@ -24,6 +24,10 @@ if __name__ == '__main__':
parser.add_option( "--indir", dest="indir", default=None, parser.add_option( "--indir", dest="indir", default=None,
help="Verzeichnis mit tische.ini und bestellung.xml; " help="Verzeichnis mit tische.ini und bestellung.xml; "
"dort werden auch TischePersonen.txt und PersonenTische.csv abgelegt" ) "dort werden auch TischePersonen.txt und PersonenTische.csv abgelegt" )
parser.add_option( "--tosvg", dest="tosvg", action="store_true", default=False,
help="zusaetzlich eine Sitzplatzverteilung.svg im --indir Verzeichnis "
"erzeugen (Tische als weisse Kreise, Personen als farbige "
"Kreise je Gruppe)" )
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if options.indir is None: if options.indir is None:
@@ -52,6 +56,7 @@ if __name__ == '__main__':
# lade Ausgabedateien # lade Ausgabedateien
AusgabeTP= os.path.join( InDir, 'TischePersonen.txt' ) AusgabeTP= os.path.join( InDir, 'TischePersonen.txt' )
AusgabePT = os.path.join( InDir, 'PersonenTische.csv' ) AusgabePT = os.path.join( InDir, 'PersonenTische.csv' )
AusgabeSVG = os.path.join( InDir, 'Sitzplatzverteilung.svg' )
Z = Zyklus() Z = Zyklus()
Z.laden( Zyklusconfig, Zyklusart ) Z.laden( Zyklusconfig, Zyklusart )
@@ -76,6 +81,8 @@ if __name__ == '__main__':
S = F1.Bester() S = F1.Bester()
print(S) print(S)
S.speichern( AusgabeTP, AusgabePT ) S.speichern( AusgabeTP, AusgabePT )
if options.tosvg:
S.alsSVG( AusgabeSVG )
else: else:
pass pass
+5
View File
@@ -20,6 +20,8 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'libs'))
from Strukturdaten import Gruppe, Gruppen, Person, Sitzplatzverteilung, Strafliste, Tisch, Tische from Strukturdaten import Gruppe, Gruppen, Person, Sitzplatzverteilung, Strafliste, Tisch, Tische
IMAGES_DIR = os.path.join(os.path.dirname(__file__), '..', 'doc', 'images')
def erzeuge_personen(anzahl, start_id=1): def erzeuge_personen(anzahl, start_id=1):
return [Person(i, 'Vorname%d' % i, 'Nachname%d' % i) for i in range(start_id, start_id + anzahl)] return [Person(i, 'Vorname%d' % i, 'Nachname%d' % i) for i in range(start_id, start_id + anzahl)]
@@ -48,6 +50,7 @@ class TestDreiTischeKettePaare(unittest.TestCase):
def test_alle_paare_komplett_an_einem_tisch(self): def test_alle_paare_komplett_an_einem_tisch(self):
SV = Sitzplatzverteilung( Tische=self.Tische, Gruppen=self.Gruppen, SV = Sitzplatzverteilung( Tische=self.Tische, Gruppen=self.Gruppen,
Strafen=self.Strafen, VIPListe={}, VIPs=Gruppe(0) ) Strafen=self.Strafen, VIPListe={}, VIPs=Gruppe(0) )
SV.alsSVG( os.path.join(IMAGES_DIR, 'drei_tische_kette_paare.svg') )
for G in (self.G1, self.G2, self.G3): for G in (self.G1, self.G2, self.G3):
self.assertEqual( SV.GruppenTeilungGet(G), 0, self.assertEqual( SV.GruppenTeilungGet(G), 0,
@@ -87,6 +90,7 @@ class TestVierTischeLinieViergruppen(unittest.TestCase):
def test_viergruppen_bleiben_auf_zusammenhaengendem_tischpaar(self): def test_viergruppen_bleiben_auf_zusammenhaengendem_tischpaar(self):
SV = Sitzplatzverteilung( Tische=self.Tische, Gruppen=self.Gruppen, SV = Sitzplatzverteilung( Tische=self.Tische, Gruppen=self.Gruppen,
Strafen=self.Strafen, VIPListe={}, VIPs=Gruppe(0) ) Strafen=self.Strafen, VIPListe={}, VIPs=Gruppe(0) )
SV.alsSVG( os.path.join(IMAGES_DIR, 'vier_tische_linie_viergruppen.svg') )
# jede Gruppe wird genau einmal geteilt (2 Tische a 2 Plaetze) # jede Gruppe wird genau einmal geteilt (2 Tische a 2 Plaetze)
self.assertEqual( SV.GruppenTeilungGet(self.G1), 1 ) self.assertEqual( SV.GruppenTeilungGet(self.G1), 1 )
@@ -138,6 +142,7 @@ class TestSechsTischeKreisDreiergruppen(unittest.TestCase):
def test_dreiergruppen_je_einmal_auf_nachbartische_geteilt(self): def test_dreiergruppen_je_einmal_auf_nachbartische_geteilt(self):
SV = Sitzplatzverteilung( Tische=self.Tische, Gruppen=self.Gruppen, SV = Sitzplatzverteilung( Tische=self.Tische, Gruppen=self.Gruppen,
Strafen=self.Strafen, VIPListe={}, VIPs=Gruppe(0) ) Strafen=self.Strafen, VIPListe={}, VIPs=Gruppe(0) )
SV.alsSVG( os.path.join(IMAGES_DIR, 'sechs_tische_kreis_dreiergruppen.svg') )
# 4 Dreiergruppen = 12 Personen auf 6x2 = 12 Plaetzen -> alle Tische voll. # 4 Dreiergruppen = 12 Personen auf 6x2 = 12 Plaetzen -> alle Tische voll.
# (SV.Tische ist eine Kopie der urspruenglichen Tische, siehe # (SV.Tische ist eine Kopie der urspruenglichen Tische, siehe