Шахматни фенове

Предадени решения

Краен срок:
29.11.2022 18:00
Точки:
10

Срокът за предаване на решения е отминал

Встъпление

Всички шахматни фенове обичат "FEN"-ове.
FEN (Forsyth–Edwards Notation) е стандартен формат за описание на позиция на фигури върху шахматна дъска. С него можем да дефинираме всяка позиция чрез символен низ със сравнително къса дължина.

Например, ето я нотацията, с която се описва началната позиция за всяка партия:
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

Нотацията съдържа:
- Позиция на фигури върху дъската - "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
- Цвят на фигурите, които са на ход - "w"
- Права за рокада на двата цвята в двете възможни посоки - "KQkq"
- Права за направа на ход "Ан-Пасан" - "-"
- Брой ходове преди последно взетата пешка - "0"
- Общ брой ходове - "1"

Понеже не сме садисти, ще пренебрегнем всички параметри освен първия. Нека приемем, че за текущата задача "FEN" е просто дефиниция на позицията на фигури върху дъската:
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR.

По-подробно

Дефиницията представлява 8 низа, отделени с "/", всеки от които показва фигурите на един ред от дъската. Редовете се именуват с цифри - започват от ред 8 и свършват с ред 1.

Всеки един самостоятелен ред съдържа 8 полета - по едно за всяка колона. Колоните се именуват с първите 8 букви от латинската азбука - започват от "A" и завършват с "H". Всяко поле показва коя е фигурата, която седи на конкретната комбинация от ред и колона.

Например, в дефиницията на началната позиция по-горе, на ред 8, колона "D" седи фигура "q", а на ред 1, колона Е седи фигура "K".

Когато на даден ред има полета, които са празни, използва се съответната цифра, за да се дефинира колко празни полета има. В началната дефиниция по-горе се вижда, че редове 6, 5, 4 и 3 са празни и тъй като всеки един от тези редове има по 8 празни полета, редовете са дефинирани просто с дефиниция "8".

Ако разгледаме тази дефиниция - rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR, която се получава като от началната позиция преместим пешка пред белия цар с две полета напред, ще видим, че ред 4 вече не съдържа просто "8", защото на него вече има пешка. Дефиницията за този ред ("4P3") означава, че четирите колони "A"-"D" са празни, след което в колона "E" има фигура "P", след което трите колони "F"-"H" са празни. Съответно, ако разгледаме ред 2, ще видим, че той съдържа едно празно поле в колона "E" - полето, от което сме взели пешката, за да я преместим с две полета напред.

Фигури

Досега говорим за фигурите като просто букви, но ето какво значат тези букви:
- "R/r" - Rook (топ)
- "N/n" - Knight (кон) /използва се "N", а не "K", защото се припокриват с царя/
- "B/b" - Bishop (офицер)
- "Q/q" - Queen (дама)
- "K/k" - King (цар)
- "P/p" - Pawn (пешка)

Малка буква означава черна фигура, а главна буква означава бяла фигура.

Точки

Освен това, в шаха е прието всяка фигура да има определен брой точки, за да може по-лесно да се определи дисбаланс при размяна на фигури и да се направи оценка на това дали текущата позиция е равна, или един от двамата играчи има преимущество.
- Топ - 5
- Кон - 3
- Офицер - 3
- Дама - 9
- Цар - 4 /не винаги има смисъл да се слага оценка на царя, но това е общоприетата стойност/
- Пешка - 1

Задачата е...

(0) Да се дефинира ChessException, което последващите класове могат да използват. Няма конкретни изисквания освен дефиницията да бъде налична за импортиране от тестовете ни, т.е. да бъде дефинирана в глобалната област на видимост на решението ви.

(1) Да се напише клас ChessPosition, който приема един аргумент при инициализация - "FEN" дефиниция в символен низ (str). Класът трябва да прави следните проверки при инициализация. Ако някое от тези правила не е изпълнено, класът трябва да възбуди изключение от тип ChessException с посочения текст.
- Позиция, в която двата царя са един до друг, е невалидна и трябва да породи изключение с текст "kings". Счита се, че двата царя са един до друг, ако полетата, върху които стоят, са съседни (включително ако са съседни по диагонал, т.е. допират ъглите си).
- Позиция, в която има повече от един цар за всеки цвят, или такава, в която няма цар за някой от цветовете, е невалидна и трябва да породи изключение с текст "kings". С други думи, всяка позиция трябва да има по точно един бял и един черен цар.
- Позиция, в която някоя пешка се намира на ред 1 или ред 8, е невалидна и трябва да породи изключение с текст "pawns".

*В случай, че позицията е невалидна по повече от едно от тези условия, например няма нито един цар, но освен това има пешка на ред 1, изключението, което трябва да се възбуди, е "kings".
**Всякакви други позиции се приемат за валидни, дори да не са реални позиции за шах. Например позиция, в която има повече от 16 фигури от един цвят, не би била валидна в партия шах, но по нашите правила е позволена.

Класът трябва да имплементира следните методи:
- get_white_score() - връща обект от тип ChessScore (виж по-долу), асоцииран с всички бели фигури на позицията.
- get_black_score() - връща обект от тип ChessScore (виж по-долу), асоцииран с всички черни фигури на позицията.
- white_is_winning() - връща True, ако сумата на точките от белите фигури в позицията е по-голяма от тази на черните. В противен случай връща False.
- black_is_winning() - връща True, ако сумата на точките от черните фигури в позицията е по-голяма от тази на белите. В противен случай връща False.
- is_equal() - връща True, ако сумата на точките от черните фигури в позицията е равна на тази на белите. В противен случай връща False.

Освен това, класът трябва да позволява следните операции с неговите инстанции:

position = ChessPosition(some_fen) # Просто инстанция за примера

print(position) # Трябва да принтира символния низ на "FEN"-а, който е получен при инициализация.

len(position) # Трябва да връща броя фигури, които позицията съдържа. Например, началната позиция на шахматна партия съдържа 32 фигури. Ако инстанцията е създадена с "FEN", който съдържа само 5 фигури, обаче, резултатът от този ред трябва да е числото 5.

position['E2'] # Трябва да връща дефиниция на фигурата, която се намира на съответната комбинация от колона и ред, дефинирана като `str`. В този пример искам фигурата, която седи на ред "2", колона "E". Ако приемем, че това е първоначалната шахматна позиция, на това поле седи бяла пешка, така че очакваният отговор е "P". Ако на въпросното поле няма никаква фигура, очакваният резултат е None.

(2) Да се напише клас ChessScore, който приема списък от фигури като аргумент при инициализация. Списъкът трябва да е от тип list, а всеки елемент от списъка да е от тип str. Всеки елемент от списъка представлява дефиниция на фигура с малка буква - едно от ('r', 'n', 'b', 'k', 'q', 'p').
Очаква се класът да пресметне общия брой точки, асоциирани с тези фигури. За разлика от по-горния клас, в който дефинирахме рестрикции за валидност на позицията, този клас трябва да работи с каквато и да било комбинация от фигури, стига те да са в списъка с валидни фигури по-горе. С други думи, трябва да мога да инстанцирам класа дори със списък, който съдържа десетки царе, или списък, който не съдържа никакви царе.

Класът трябва да позволява следните операции с неговите инстанции.

# Просто дефиниция на инстанции, които да използвам за примерите
score1 = ChessScore(some_pieces1)
score2 = ChessScore(some_pieces2)

int(score1) # Трябва да връща общия брой точки, асоциирани с фигурите, подадени при инициализиране. Например int(ChessScore(['r', 'b'])) трябва да дава резултат 8 (5 за топ и 3 за офицер).

# Следните редове трябва да са валиден синтаксис за сравнение на общия брой точки на две инстанции
score1 < score2
score1 <= score2
score1 == score2
score1 != score2
score1 > score2

# Следните редове трябва да са валиден синтаксис за операции с общия брой точки на две инстанции
score1 + score2
score1 - score2