# Mecze w wielu formatach — projekt

Aplikacja obsługuje dziś jeden format: cztery moje drużyny przeciw czterem
cudzym, punkty ligowe z miejsc, jeden mecz na kolejkę. Ten dokument opisuje
rozszerzenie o mecze doraźne w trzech dodatkowych formatach.

## 1. Cel

Poza ligą 4x4 właściciel rozgrywa co kolejkę pojedyncze mecze: jeden na
jednego, duety rozstrzygane sumą punktów pary oraz trójki, w których o wyniku
decydują trzy osobne pojedynki. Te mecze nie tworzą lig — nie mają stałego
składu ani bilansu sezonu. Właściciel dodaje je sam, kolejka po kolejce,
wskazując obie strony.

Wszystkie mecze danej kolejki mają być widoczne na jednej stronie i korzystać z
tych samych danych FPL.

## 2. Formaty

| Format | Skład | Rozstrzygnięcie | Punkty ligowe | Bilans sezonu |
|---|---|---|---|---|
| Pojedynek | 1 | wyższy dorobek FPL | nie | nie |
| Duety | 2 | wyższa suma pary | nie | nie |
| Trójki | 3 | więcej wygranych pojedynków | nie | nie |
| 4x4 | 4 | miejsca od 7 w dół, 28 do podziału | tak | tak |

Rozmiar składu wynika z formatu i nie podlega konfiguracji.

Trzy nowe formaty zapisują wyłącznie rozstrzygnięcie — zwycięstwo, remis albo
porażkę. Punkty ligowe pozostają wyłącznością 4x4, bo tylko tam mają sens:
biorą się z podziału puli między osiem drużyn.

### Pojedynek

Moja drużyna przeciw ich drużynie. Wygrywa wyższy dorobek FPL po odjęciu kary
za transfery. Równe dorobki to remis.

### Duety

Suma dwóch moich drużyn przeciw sumie dwóch ich drużyn. Rozstrzygnięcie jak
wyżej, tylko liczby są sumami.

### Trójki

Trzy niezależne pojedynki. Pary wskazuje właściciel przy dodawaniu meczu — nie
wynikają z kolejności składu, bo ustala się je z rywalem przed kolejką.

Pojedynek wygrywa wyższy dorobek; przy równych dorobkach pojedynek przepada obu
stronom. Mecz wygrywa strona z większą liczbą wygranych pojedynków; równa
liczba to remis. Wynik 1–1 przy jednym pojedynku nierozstrzygniętym jest więc
remisem meczu.

### 4x4

Bez zmian. Osiem drużyn ustawia się według dorobku, pierwsza dostaje 7 punktów,
ostatnia 0, remisy dzielą sumę miejsc po równo, a pula zawsze wynosi 28. Skład
mojej strony bierze się ze stałej czwórki, jak dziś.

## 3. Model danych

### Mecz

`LeagueFixture` zyskuje format. To jedyna zmiana strukturalna po stronie meczu —
nie powstaje żaden byt „rozgrywek", bo nowe mecze nie mają ciągłości przez
sezon.

Ograniczenie unikalności kolejki znika. Zamiast niego obowiązuje reguła
sprawdzana przy zapisie: **w jednej kolejce może istnieć najwyżej jeden mecz
4x4**, ale meczów doraźnych dowolna liczba — właściciel może w tej samej
kolejce rozegrać kilka pojedynków z różnymi rywalami.

### Skład

Byt `Squad` traci stałą `SIZE = 4`. Oczekiwany rozmiar podaje format przy
sprawdzaniu kompletności. Reszta bez zmian.

**Każdy mecz dostaje własne składy obu stron.** Dziś mecz 4x4 wskazuje na ten
sam wiersz składu, który edytuje ekran „moja czwórka", więc zmiana stałej
czwórki po cichu przepisuje historię rozegranych kolejek. Przy meczach
doraźnych skład i tak jest wskazywany za każdym razem od nowa, więc kopiowanie
staje się regułą jednolitą dla wszystkich formatów: mecz zapisuje, kto
faktycznie grał w tej kolejce.

Stała czwórka zostaje tym, czym była — wzorcem, z którego formularz nowego
meczu 4x4 wypełnia moją stronę.

### Pojedynki

Nowy byt `Duel` używany wyłącznie w trójkach: mecz, numer pojedynku od 1 do 3,
moja drużyna i drużyna rywala. Para mecz–numer jest unikalna.

Reguła spójności: w trzech pojedynkach każda z moich trzech drużyn występuje
dokładnie raz i tak samo każda z trzech drużyn rywala. Sprawdzane przy zapisie,
nie tylko w formularzu.

## 4. Liczenie wyniku

Dzisiejszy `LeagueScorer` obsługuje jeden format i zakłada osiem drużyn.
Wprowadzamy wspólny kontrakt liczenia wyniku, z osobną implementacją na format,
wybieraną po formacie meczu.

Wynik meczu niesie: wiersze dla każdej drużyny z jej dorobkiem FPL, liczbę dla
mojej i dla ich strony, podpis mówiący co ta liczba znaczy, rozstrzygnięcie i
różnicę. Podpis zależy od formatu — w 4x4 liczba to punkty ligowe, w pojedynku
i duetach dorobek FPL, w trójkach liczba wygranych pojedynków. Dzięki temu
jeden szablon obsługuje wszystkie formaty bez rozgałęzień na każdym kroku.

Trójki niosą dodatkowo wynik każdego pojedynku z osobna, bo bez tego rezultat
2–1 nic nie mówi.

Różnice w składach liczą się tak jak dziś — po liczbie drużyn wystawiających
danego zawodnika, z pominięciem zawodników wystawionych przez obie strony tak
samo licznie. Mechanizm jest niezależny od formatu.

## 5. Ekrany i nawigacja

### Strona kolejki

Adres `/kolejka/{numer}` zastępuje dzisiejsze `/fixture/{numer}`. Pokazuje
wszystkie mecze tej kolejki, jeden pod drugim: najpierw 4x4, potem mecze
doraźne w kolejności dodania. Każdy blok to nagłówek z formatem, wynik, tabela
drużyn, różnice w składach i składy — czyli to, co dziś widać na stronie meczu,
powielone na mecze.

Trójki mają dodatkowo listę trzech pojedynków z wynikiem każdego.

Kolejka bez żadnego meczu pokazuje krótką informację zamiast błędu 404 — przy
meczach doraźnych pusta kolejka przestaje być pomyłką w adresie, a staje się
zwyczajnym stanem.

Adres `/teraz` działa jak dziś: przekierowuje na stronę bieżącej kolejki.
Zewnętrzny cron odświeżający dane celuje właśnie w niego, więc jednym
zapytaniem odświeża wszystkie mecze kolejki naraz.

### Bilans sezonu

Bez zmian: liczy wyłącznie mecze 4x4. Mecze doraźne nie tworzą ciągłości, więc
nie mają czego bilansować.

### Nowy mecz

Formularz zyskuje na wstępie wybór formatu. Liczba pól po obu stronach i
obecność sekcji pojedynków wynikają z formatu.

W 4x4 moja strona wypełnia się ze stałej czwórki i pozostaje do podejrzenia. W
pozostałych formatach obie strony wskazuje się tak samo — wyszukiwaniem po
nazwie wśród drużyn zapisanych w bazie, jak dziś dla rywali.

### Menu

Bez przebudowy. Pozycje jak dziś: Bilans, Nowy mecz, Moja czwórka, Kod, z tym
że „Bilans" i strona kolejki prowadzą teraz do widoku obejmującego wszystkie
mecze.

## 6. Reguły sprawdzania przy dodawaniu meczu

Formularz odrzuca zapis, gdy:

1. wskazana kolejka nie istnieje w słownikach,
2. dodawany jest mecz 4x4, a ta kolejka ma już mecz 4x4,
3. w 4x4 stała czwórka jest niekompletna,
4. liczba wskazanych drużyn po którejkolwiek stronie nie zgadza się z formatem,
5. drużyny powtarzają się w obrębie strony,
6. ta sama drużyna występuje po obu stronach,
7. wskazana drużyna nie istnieje w bazie,
8. w trójkach pary nie pokrywają każdej z sześciu drużyn dokładnie raz.

Zapis idzie w transakcji. Wyścig dwóch jednoczesnych zapisów meczu 4x4 w tej
samej kolejce kończy się czytelnym komunikatem, a nie błędem bazy.

## 7. Migracja danych

Jedna migracja, wykonywana na bazie z rozegraną pierwszą kolejką:

1. powstaje tabela pojedynków,
2. mecz zyskuje kolumnę formatu, a istniejące mecze dostają format czteroosobowy,
3. znika ograniczenie unikalności kolejki.

Migracja nie kasuje żadnych danych. Kopiowanie składu dotyczy meczów dodawanych
po wdrożeniu — istniejący mecz zostaje przy dzisiejszym powiązaniu, bo rozbicie
go wstecz nie wniosłoby nic poza ryzykiem.

## 8. Czego ta zmiana nie obejmuje

- Bilansu sezonu dla meczów doraźnych.
- Edycji ani kasowania dodanych meczów.
- Tabel ligowych obejmujących innych graczy.
- Powiadomień o wynikach.

## 9. Testowanie

Liczenie wyniku każdego formatu dostaje własne testy jednostkowe, w tym
przypadki brzegowe: remis w pojedynku, remis sumy w duetach, trójki z
nierozstrzygniętym pojedynkiem, trójki 2–1 i 1–1–1.

Sprawdzanie spójności pojedynków dostaje testy na wszystkie odrzucane układy par.

Dodawanie meczu dostaje testy integracyjne na każdą z ośmiu reguł oraz na
wyścig dwóch zapisów meczu 4x4.

Strona kolejki dostaje testy funkcjonalne: kilka meczów na jednej stronie,
kolejka pusta, kolejka z samymi trójkami, dwa pojedynki w jednej kolejce.

Migracja dostaje test sprawdzający, że dane sprzed niej zachowują wynik meczu
pierwszej kolejki.
