Недавно я выложил свою первую статью, в которой рассказал об опыте создания шахмат на двоих на pygame. Там я встретил большое количество объективной критики: ошибок было действительно много. И поэтому сегодня попытаюсь исправить все свои косяки, написав чистый и структурированный код с учётом всей критики. Приступим
Создадим матрицу соответствующую доске, где точка - пустая клетка
Board=[['.']*8 for y in range(8)]
Теперь создадим класс фигур
class ChessPiece():
def __init__(self, name, color):
self.color=color
self.already_moved=False
self.name=name
def __str__(self):
return self.name+self.color
Для создания экземпляра класса будем вписывать имя и цвет. Имя - латинское обозначение фигуры в стандартной шахматной нотации (исключение: P-пешка). Для обозначения цвета примем 0 за белый, 1 - за чёрный.
Мы уже можем добавлять в матрицу доски экземпляры класса ChessPiece, но сделаем это позже.
Хорошо бы создать в классе метод, возвращающий список доступных фигуре ходов. Для этого создадим словарь attack_dict. Сейчас объясню, зачем он
attack_dict={'R':[[0,1],[1,0],[0,-1],[-1,0],1],
'B':[[1,1],[-1,-1],[1,-1],[-1,1],1],
'Q':[[1,1],[-1,-1],[1,-1],[-1,1],[0,1],[1,0],[0,-1],[-1,0],1],
'N':[[1,2],[2,1],[-1,-2],[-2,-1],[-1,2],[-2,1],[1,-2],[2,-1],0],
'K':[[1,1],[-1,-1],[1,-1],[-1,1],[0,1],[1,0],[0,-1],[-1,0],0],}
Объясняю: введя имя фигуры в качестве ключа для словаря получим список. Все значения кроме последнего - направление атаки, они показывают смещение по X и Y которое нужно совершить для получения клетки, которую атакует фигура. Последнее же значение показывает длину атаки. Если 1, то фигура атакует на всё поле, если 0, то единожды.
Пора добавить в класс метод get_moves, он вернёт список доступных фигуре ходов.
Этот же метод в последующем будем использовать для проверки на шах. Значит, если обнаружим, что атакуем вражеского короля, то вернём True вместо списка ходов, ведь возможность срубить короля в игре всё равно не представляется
def get_moves(self, x, y):
moves=[]
piece=Board[y][x]
attack=attack_dict[piece.name][0:-1]
for shift in attack: #shift - смещение о котором говорил ранее
pos=[x,y]
for i in range(attack_dict[piece.name][-1]*6+1): #если атака на всё поле, то цикл повторится 7 раз, иначе - 1
pos[0]+=shift[0]
pos[1]+=shift[1]
if pos[0]>7 or pos[0]<0 or pos[1]>7 or pos[1]<0: break #вышли за поле-стоп
under_attack=Board[pos[1]][pos[0]]
if under_attack!='.':
if under_attack.name=='K' and under_attack.color!=piece.color:return True #если бьём короля, вернём True
elif under_attack.color!=piece.color: moves.append(pos[:])
break
moves.append(pos[:])
return moves
Обратите внимание, что пешки - существа странные. Их ходы и необычные свойства слишком выбиваются, относительно остальных фигур. По этой причине их нет в attack_dict. Значит, для пешек мы создадим свой подкласс Pawn и свой метод get_moves
class Pawn(ChessPiece):
def __init__(self, name, color):
self.color=color
self.already_moved=False
self.name=name
def get_moves(self, x, y):
moves=[]
pos=[x,y]
if self.color=='1': y+=1
else: y-=1
x-=1 #сместимся по диагонали
for i in range(2):
if 7>=y>=0 and 7>=x>=0:
if Board[y][x]!='.' and Board[y][x].color!=self.color:
moves.append([x,y][:]) #если в клетке стоит враг-добавить, как вариант хода
if Board[y][x].name=='K': return True #проверка на шах
x+=2 #проверим другую диагональ
x,y=pos[0], pos[1] #вернём x и y старые значения
for i in range(2-self.already_moved): #добавим ходы без взятия (если пешку ещё не трогали, то 2 клетки сразу)
if self.color=='1': y+=1
else: y-=1
if y>7 or y<0: break
if Board[y][x]!='.': break
moves.append([x,y][:])
return moves
Сделаем функцию - проверку на шах
def check_shah(B_W): #если B_W равен 0, то интересует шах белым, 1-чёрным
for y in range(8):
for x in range(8):
if Board[y][x]!='.' and Board[y][x].color!=B_W:
if Board[y][x].get_moves(x,y)==True: return True
return False
Но не все полученные ходы с функции get_moves приемлемы. Нужно отбросить те, после которых король под шахом. Напишем соответствующую функцию
def filter_moves(x,y):
piece=Board[y][x]
moves=piece.get_moves(x,y)
Board[y][x]='.' #уберем фигуру с поля
for_deletion=[]
for move in moves:
remember=Board[move[1]][move[0]] #запомним клетку, куда сейчас поставим фигуру
Board[move[1]][move[0]]=piece #ставим
if check_shah(piece.color): for_deletion.append(move) #если король под шахом-записать этот ход на удаление
Board[move[1]][move[0]]=remember #возвращаем всё как было
Board[y][x]=piece
for delet in for_deletion: #удалим лишние ходы
moves.remove(delet)
return moves
На очереди функция проверки на мат или пат
def checkmate_stalemate(B_W):
for y in range(8):
for x in range(8):
if Board[y][x]!='.' and Board[y][x].color==B_W:
if filter_moves(x,y)!=[]: return None #ходы есть, значит мата/пата нет
if check_shah(B_W): return 1 #мат
return 0 #пат
Основные функции почти готовы. Давайте займемся импортом библиотек, созданием окна и игрового цикла
import pygame
from pygame import *
import pygame as pg
import math
wind=display.set_mode((640,640))
display.set_caption('Chess')
clock=time.Clock()
font.init()
game=True
while game:
for e in event.get():
if e.type==QUIT:
game=False
clock.tick(60)
Не хватает отрисовки доски. Добавим в папку с игрой картинки, называющиеся в соответствии с названием и цветом фигуры. Пример: N0.png - белый конь, Q1.png - черная королева, P1.png-чёрная пешка и т.д.
RectList=[] #список белых клеточек
for i in range(8):
for n in range(4):
RectList.append(pygame.Rect((n*160+(i%2)*80,i*80, 80, 80)))
def draw_board():
pygame.draw.rect(wind, (181, 136, 99), (0, 0, 640, 640)) #одна большая черная клетка
for R in RectList:
pygame.draw.rect(wind, ((240, 217, 181)), R) #много маленьких белых клеток
for y in range(8):
for x in range(8):
if Board[y][x]!='.':
wind.blit(transform.scale(pygame.image.load(Board[y][x].__str__()+'.png'),(70,70)),(5+x*80,5+y*80))
#рисуем фигуры
display.update()
Ещё немного декора: добавим функцию, рисующую кружочки-подсказки для хода
def draw_circles(moves):
for circle in moves:
pygame.draw.circle(wind, (200,200,200), (circle[0]*80+40, circle[1]*80+40), 10)
display.update()
Теперь функция, выводящая на экран победителя или ничью
def try_print_winner(turn):
check=checkmate_stalemate(str(turn))
if check!=None: #если мат или пат, выведем это на экран
draw_board()
if check==1 and turn==0:
wind.blit(pygame.font.SysFont(None,30).render('BLACK WON', False,(30, 30, 30)),(260,310))
elif check==1 and turn==1:
wind.blit(pygame.font.SysFont(None,30).render('WHITE WON', False,(30, 30, 30)),(260,310))
else:
wind.blit(pygame.font.SysFont(None,30).render('DRAW', False,(30, 30, 30)),(290,310))
display.update()
Займемся перемещением фигур игроком. Для простоты будем хватать фигуру нажатием мыши и перемещать отжатием. Вот как выглядит игровой цикл сейчас:
turn=0 #turn показывает, чья очередь
draw_board()
game=True
while game:
for e in event.get():
if e.type==QUIT:
game=False
if e.type==pg.MOUSEBUTTONDOWN and e.button==1: #Если нажата ЛКМ
x_from,y_from=(e.pos)
x_from,y_from=math.floor(x_from/80),math.floor(y_from/80)
if Board[y_from][x_from]!='.' and Board[y_from][x_from].color==str(turn):
moves=filter_moves(x_from,y_from) #получаем ходы для фигуры
draw_circles(moves)
else: x_from=-1
else: x_from=-1
if e.type==pg.MOUSEBUTTONUP and e.button==1 and x_from!=-1: #если отжата ЛКМ
x_to,y_to=(e.pos)
x_to,y_to=math.floor(x_to/80),math.floor(y_to/80)
if moves.count([x_to,y_to]): #если ход приемлем, переставляем фигуру
Board[y_to][x_to]=Board[y_from][x_from]
Board[y_to][x_to].already_moved=True
Board[y_from][x_from]='.'
turn=1-turn #меняем ход
draw_board()
try_print_winner(turn)
clock.tick(60)
Цикл тяжеловато читается. Исправим, раскинув строчки по функциям
def grab_piece(x,y): #взять фигуру
piece=Board[y][x]
moves=[]
if piece=='.' or piece.color!=str(turn): return []
moves=filter_moves(x,y)
return moves
def put_piece(x_to,y_to,x_from,y_from,moves): #поставить фигуру
if moves.count([x_to,y_to]): #если ход приемлем, переставляем фигуру
Board[y_to][x_to]=Board[y_from][x_from]
Board[y_to][x_to].already_moved=True
Board[y_from][x_from]='.'
return True #флаг, который подскажет сменить ход
А вот и простой, понятный игровой цикл:
while game:
for e in event.get():
if e.type==QUIT:
game=False
if e.type==pg.MOUSEBUTTONDOWN and e.button==1: #Если нажата ЛКМ
x_from,y_from=(e.pos)
x_from,y_from=math.floor(x_from/80),math.floor(y_from/80)
moves=grab_piece(x_from,y_from)
draw_circles(moves)
if e.type==pg.MOUSEBUTTONUP and e.button==1: #если отжата ЛКМ
x_to,y_to=(e.pos)
x_to,y_to=math.floor(x_to/80),math.floor(y_to/80)
if put_piece(x_to,y_to,x_from,y_from,moves):
turn=1-turn #меняем ход
draw_board()
try_print_winner(turn)
clock.tick(60)
Кстати, помните функцию checkmate_stalemate? Это проверка на мат или пат. Так вот, она использует другую функцию - filter_moves для получения списка ходов. Я заменю filter_moves на функцию grab_piece (сейчас она делает почти тоже самое) так как скоро мы добавим в grab_piece ещё ходы и filter_moves потеряет актуальность.
Это было важное отступление, а сейчас добавим все фигуры на доску
for i in range(8):
Board[0][i]=ChessPiece('RNBQKBNR'[i],'1')
Board[1][i]=Pawn('P','1')
Board[7][i]=ChessPiece('RNBQKBNR'[i],'0')
Board[6][i]=Pawn('P','0')
Игра готова за исключением трёх вещей. Не хватает рокировки, взятия на проходе и превращения пешки по достижению края доски. Рекомендую почитать, как совершаются эти ходы по ссылке, если вы не в курсе.
Рокировка-ход королём, да при том весьма необычный. Так что давайте создадим отдельный подкласс для короля, за одно напишем функцию, добавляющую рокировку
class King(ChessPiece):
def add_castling(self):
castlings_list=[]
if self.already_moved==False:
y=7-int(self.color)*7
rooks_cond=[False,False] #проверка ладей
for x in range(2):
check_piece=Board[y][x*7]
if check_piece!='.':
if check_piece.name==('R') and check_piece.color==self.color and check_piece.already_moved==False:
rooks_cond[x]=True
reach_cond=[False,False] #проверка места между королём и ладьями
for x in range(2):
if x==0 and Board[y][1:4]==['.','.','.']: reach_cond[0]=True
elif x==1 and Board[y][5:7]==['.','.']: reach_cond[1]=True
shah_cond=[False,False] #проверка шаха
for x in range(2):
#будем ставить временных королей, если они под шахом-условие не соблюдено
if x==0 and reach_cond[x]:
Board[y][2],Board[y][3]=King('K',self.color),King('K',self.color)
if check_shah(self.color)==False: shah_cond[x]=True
Board[y][2],Board[y][3]='.','.'
elif x==1 and reach_cond[x]:
Board[y][5],Board[y][6]=King('K',self.color),King('K',self.color)
if check_shah(self.color)==False: shah_cond[x]=True
Board[y][5],Board[y][6]='.','.'
all_cond=(shah_cond[0] and rooks_cond[0],shah_cond[1] and rooks_cond[1])
if all_cond[0]: castlings_list.append([2,y])
if all_cond[1]: castlings_list.append([6,y])
return castlings_list
Пока что, метод add_castling проверяет все условия для рокировки, затем возвращает клетки, куда нужно сходить королю для рокировки. Давайте дадим королю возможность ходить на эти клетки. Для этого добавим в конец функции grab_piece следующие строчки:
if piece.name=='K':
global castlings
#castlings ещё понадобится, поэтому создадим его перед игр. циклом, а тут глобализуем
castlings=piece.add_castling()
for c in castlings:
moves.append(c)
Теперь надо заставить двигаться ладью. Для этого добавим королю метод move_rook. Его будем вызывать перед тем, как двигать короля
def move_rook(self,x,y): #x,y-координаты точки, куда ходит король
if x==2: #если рокировка влево
Board[y][0]='.'
Board[y][3]=ChessPiece('R',self.color)
elif x==6: #если рокировка вправо
Board[y][7]='.'
Board[y][5]=ChessPiece('R',self.color)
Осталось всё связать в функции put_piece, переставляющей фигуры
def put_piece(x_to,y_to,x_from,y_from,moves):
if moves.count([x_to,y_to]):
#новые строчки
if Board[y_from][x_from].name=='K':
global castlings
if castlings.count([x_to, y_to]): #если ход является рокировкой
Board[y_from][x_from].move_rook(x_to,y_to) #переставим ладью
Board[y_to][x_to]=Board[y_from][x_from]
Board[y_to][x_to].already_moved=True
Board[y_from][x_from]='.'
return True
Кстати, поскольку мы вынесли короля в отдельный класс, то не забудем сделать это и при создании данной фигуры:
for i in range(8): #создание всех фигур кроме королей
Board[0][i]=ChessPiece('RNBQ.BNR'[i],'1')
Board[1][i]=Pawn('P','1')
Board[7][i]=ChessPiece('RNBQ.BNR'[i],'0')
Board[6][i]=Pawn('P','0')
Board[0][4]=King('K','1') #создание двух королей
Board[7][4]=King('K','0')
Рокировка готова! На очереди взятие на проходе.
Как же его реализовать? Сейчас попытаюсь объяснить свою идею. Для этого рассмотрим следующую ситуацию:
![](https://habrastorage.org/getpro/habr/upload_files/03c/16a/23d/03c16a23dad9c41a21c7895967f19206.png)
После хода белой пешки, вызовем некий метод check_en_passant класса Pawn, который, если пешка сходила на 2 клетки, запишет клетку справа и слева в список en_passant_pos. На следующем ходу вражеские пешки на этих клетках (если они там есть) смогут совершить взятие на проходе.
В en_passant_pos надо добавить ещё кое-что: клетку, отмеченную красной точкой (туда будем ходить при взятии) и клетку, где сейчас стоит белая пешка (её будем рубить)
en_passant_pos создали перед игровым циклом, а метод check_en_passant - сейчас:
def check_en_passant(x_from,y_from,x_to,y_to):
if y_from-y_to==2 or y_from-y_to==-2:#если ходим на 2 клетки
global en_passant_pos
en_passant_pos=[[x_to-1,y_to],[x_to+1,y_to]] #клетки слева и справа от пешки
en_passant_pos.append(x_to, (y_from+y_to)//2) #куда ходим при взятии?
#(найдя среднее между Y до и после хода пешки, получим Y нужной клетки)
en_passant_pos.append(x_to,y_to) #клетка где стоит наша пешка
По-моему, если получать значения из en_passant_pos по индексу, то это будет тяжело для восприятия, так что заменим его на словарь. Значение будем получать по "кодовому слову".
Вот как теперь выглядит check_en_passant:
def check_en_passant(self, x_from,y_from,x_to,y_to):
if y_from-y_to==2 or y_from-y_to==-2:#если ходим на 2 клетки
global en_passant_pos
en_passant_pos['left']=[x_to-1,y_to] #клетка слева от пешки
en_passant_pos['right']=[x_to+1,y_to] #справа
en_passant_pos['move']=[x_to, (y_from+y_to)//2] #куда ходим при взятии?
en_passant_pos['fellt']=[x_to,y_to] #клетка где стоит наша пешка
Теперь подумаем о том, как проверить ходы-взятия на шах после совершения. Давайте создадим метод en_passant_filt. Он, благодаря всем данным из en_passant_pos, с имитирует взятие на проходе, и проверит короля на шах. Но перед этим пара важных для понимания вещей:
Для совершения взятия на проходе нужно 3 действия: 1 - убрать рубящую пешку, 2 - поставить рубящую пешку в другую точку, 3 - убрать срубленную пешку.
При чём порядок выполнения неважен. Понимание этого сейчас пригодится, ведь мы сначала выполним пункт 2 и 3, и только после этого - 1Напомню, что en_passant_pos хранит координаты клеток, пешкам в которых можно совершать взятие. Так вот, если нужно запретить данное действие, то будем заменять координаты клетки на [-1, -1] (несуществующие)
А вот и функция en_passant_filt:
def en_passant_filt(self):
pos=en_passant_pos['fellt']
Board[pos[1]][pos[0]]='.' #уберём нашу пешку (пункт 3)
pos=en_passant_pos['move']
enemy_color=str(1-int(self.color))
Board[pos[1]][pos[0]]=Pawn('P',enemy_color) #поставим вражескую пешку (пункт 2)
piece=Board[pos[1]][pos[0]]
for cell in ['left','right']: #здесь выполним пункт 1
pos=en_passant_pos[cell]
piece=Board[pos[1]][pos[0]]
if piece=='.': en_passant_pos[cell]=[-1,-1]; continue
if piece.name!='P' or piece.color==self.color: en_passant_pos[cell]=[-1,-1]; continue
#если в проверяемой клетке не вражеская пешка-запрещаем взятие
Board[pos[1]][pos[0]]='.' #срубим пешку (пункт 1)
if check_shah(enemy_color): en_passant_pos[cell]=[-1,-1] #если после пункта 1 шах-запрещаем взятие
#если все этапы проверки пройдены, то не трогаем проверяемую клетку (она может совершить взятие)
Board[pos[1]][pos[0]]=Pawn('P', enemy_color) #вернём фигуру
Board[pos[1]][pos[0]].already_moved=True
pos=en_passant_pos['fellt'] #вернём всё как было
Board[pos[1]][pos[0]]=Pawn('P',self.color)
Board[pos[1]][pos[0]].already_moved=True
pos=en_passant_pos['move']
Board[pos[1]][pos[0]]='.'
Теперь начнём вызывать en_passant_filt в конце check_en_passant:
self.en_passant_filt()
Поздравляю, самое сложное позади. Теперь надо начать вызывать check_en_passant после каждого хода пешкой. Для этого допишем в конец функции put_piece следующее:
global en_passant_pos
en_passant_pos={} #будем очищать en_passant_pos каждый ход
if Board[y_to][x_to].name=='P':
Board[y_to][x_to].check_en_passant(x_from,y_from,x_to,y_to)
По взятию на проходе осталось всего ничего. Надо добавить пешке, которая получит разрешение на взятие, возможность его совершить. Допишем следующее в grab_piece:
if piece.name=='P':
global en_passant_pos
if len(en_passant_pos)>0:
if en_passant_pos['left']==[x,y] or en_passant_pos['right']==[x,y]:
moves.append(en_passant_pos['move'])
Сейчас пешки просто научились ходить по диагонали на пустые клетки, но взятие на проходе не зря называется взятием, начнём рубить пешку. Для этого надо дописать чуть-чуть кода в put_piece. Сделаем это:
def put_piece(x_to,y_to,x_from,y_from,moves):
if moves.count([x_to,y_to]):
if Board[y_from][x_from].name=='K':
global castlings
if castlings.count([x_to, y_to]):
Board[y_from][x_from].move_rook(x_to,y_to)
Board[y_to][x_to]=Board[y_from][x_from]
Board[y_to][x_to].already_moved=True
Board[y_from][x_from]='.'
global en_passant_pos
#новые строчки
if Board[y_to][x_to].name=='P':
if len(en_passant_pos)>0:
if en_passant_pos['move']==[x_to,y_to]:
pos=en_passant_pos['fellt']
Board[pos[1]][pos[0]]='.'
en_passant_pos={}
if Board[y_to][x_to].name=='P':
Board[y_to][x_to].check_en_passant(x_from,y_from,x_to,y_to)
return True
Наконец-то мы разобрались со взятием. Давайте подумаем о превращении пешки. Когда пешка будет доходить до края доски, нужно как-то останавливать цикл смены ходов и запускать выбор фигуры для превращения.
Для этого вспомним про переменную turn. Она принимает 0 или 1 в зависимости от того, чей ход. Так вот, предлагаю, для события превращения пешки, задавать turn значение -1. Тогда никто не сможет совершить ход, а мы реализуем свои коварные планы.
Находить пешку на краю доски и задавать turn значение -1 будем при помощи функции find_border_pawn:
def find_border_pawn():
y=-1
for x in range(8): #ищем пешку на краю доски
if Board[0][x]!='.' and Board[0][x].name=='P':
y=0; break
if Board[7][x]!='.' and Board[7][x].name=='P':
y=7; break
if y!=-1:
global turn
turn=-1
#по углам клетки, на которой стоит пешка, нарисуем ферзя, ладью, слона и коня
if Board[y][x].color=='0':
wind.blit(transform.scale(pygame.image.load('Q0.png'),(40,40)),(x*80,y*80))
wind.blit(transform.scale(pygame.image.load('R0.png'),(40,40)),(x*80+40,y*80))
wind.blit(transform.scale(pygame.image.load('B0.png'),(40,40)),(x*80,y*80+40))
wind.blit(transform.scale(pygame.image.load('N0.png'),(40,40)),(x*80+40,y*80+40))
if Board[y][x].color=='1':
wind.blit(transform.scale(pygame.image.load('Q1.png'),(40,40)),(x*80,y*80))
wind.blit(transform.scale(pygame.image.load('R1.png'),(40,40)),(x*80+40,y*80))
wind.blit(transform.scale(pygame.image.load('B1.png'),(40,40)),(x*80,y*80+40))
wind.blit(transform.scale(pygame.image.load('N1.png'),(40,40)),(x*80+40,y*80+40))
display.update()
return x,y
Кстати, пока пешка на краю доски, событие отжатия мыши нам не нужно. Поэтому я добавил в него условие, что turn не равен -1. Тут смотреть не на что.
Итак, сейчас мы имеем функцию, которая находит пешку на краю доски, рисует вокруг неё нужные фигуры, задаёт turn значение -1 и возвращает координаты пешки. Я немного поработал с этими координатами и вот как теперь выглядит игровой цикл:
while game:
for e in event.get():
if e.type==QUIT:
game=False
if e.type==pg.MOUSEBUTTONDOWN and e.button==1:
x_from,y_from=(e.pos)
x_from,y_from=math.floor(x_from/80),math.floor(y_from/80)
if turn==-1 and (x_pawn,y_pawn)==(x_from,y_from): #если нажали на пешку на краю доски
replace_pawn(x_pawn,y_pawn) #эту функцию ещё не показал
moves=grab_piece(x_from,y_from)
draw_circles(moves)
if e.type==pg.MOUSEBUTTONUP and e.button==1 and turn!=-1:
x_to,y_to=(e.pos)
x_to,y_to=math.floor(x_to/80),math.floor(y_to/80)
if put_piece(x_to,y_to,x_from,y_from,moves):
turn=1-turn
draw_board()
try_print_winner(turn)
#новая строчка-запоминаем координаты пешки
x_pawn,y_pawn=find_border_pawn()
clock.tick(60)
Функция replace_pawn - вот финальный кусок кода, который я вам покажу:
def replace_pawn(x_pawn,y_pawn):
x,y=(e.pos)
x,y=math.floor((x%80)/40), math.floor((y%80)/40)
#теперь x и y отображают угол в который нажали, по ним поймём во что превратить пешку
piece_dict={
(0,0): 'Q',
(1,0): 'R',
(0,1): 'B',
(1,1): 'N'}
piece_name=piece_dict[(x,y)]
piece_color=Board[y_pawn][x_pawn].color
Board[y_pawn][x_pawn]=ChessPiece(piece_name,piece_color)
global turn
turn=int(piece_color)
turn=1-turn
draw_board()
Вот и всё! У нас получились полностью функционирующие шахматы на двоих. Надеюсь, что положительные изменения относительно прошлой статьи заметны. Если у вас есть мысли по улучшению кода или информация об ошибках, обязательно напишите об этом в комментариях
Ссылка на Яндекс Диск с игрой
Переходите по ссылке >>> нажимайте на кнопку скачать всё >>> сохраняйте HabrChess.zip >>> открывайте его >>> перетаскивайте папку Chess себе на рабочий стол или в любое место проводника >>> открываете эту папку >>> открываете Chess.py >>> можно играть
Комментарии (7)
andyshark1974
20.08.2024 08:26+1Я в своем проекте делал что-то подобное, и пришел к выводу что нельзя рассматривать фигуры с отрывом от позиции на доске. Вы кстати на этом же попались когда делали рокировку и взятие на проходе. В итоге (сугубо мое мнение) получилось некрасиво - помесь одного с другим. Т.е. Вы сначала делаете проверку на возможность хода фигуры, потом выделяете короля в отдельную сущность, потом выделяете пешку в отдельную сущность и... Вот это и некрасиво.
Далее. Вы проверяете только пат. Но в шахматах есть кроме пата еще правила на ничью - трехкратное повторение позиции, недостаточно материала для мата, правило 50 ходов. И если последнее правило Вы можете обойти если не пишите партию, то два остальных как обойти? Это полноценные правила которые Вы игнорируете, а ведь они эквивалентны пату.
Еще про рокировку. Либо я недоглядел, но где проверка того ходила фигура или нет? Ведь это тоже запрет рокировки.
Из рекомендаций. Я бы посоветовал хранить FEN позицию, тогда проверка возможности рокировки и взятие на проходе упрощается резко и в разы.
RUSTIK1023 Автор
20.08.2024 08:26Да, рокировка и взятие на проходе, действительно, оформлены не очень красиво, признаю.
На счёт условий для ничьи Вы правы. Я поленился их добавлять, расчитывая на то, что игроки договорятся сами
А на счёт условия для рокировки о котором Вы написали, оно есть. У каждой фигуры есть параметр already_moved. Он проверяется для ладьи и короля.
А так, спасибо за критику, попытаюсь добавить то, о чём вы написали когда появятся силы.andyshark1974
20.08.2024 08:26+1Легче сразу работать по FEN-записи на самом деле. Я у себя ее просто загружаю и разбираю по свойствам внутри объекта "Шахматная доска". Просто нельзя рассматривать фигуры в отрыве от доски, это единое целое именно из-за рокировки и взятия на проходе, увы. И всякие примеры типа "наследования" в шахматной реальности не работают :-) потому что без доски - толку ноль.
Кстати, если хотите увидеть интересный код, посмотрите реализацию cm-chessboard на js.
SuharkovMP
шах = check, а у вас сейчас функция проверки персидского короля.. на что? )
RUSTIK1023 Автор
Спасибо за поправку, не знал. Но я боюсь, что если функция будет называться check_check, то смысл будет понятен ещё меньше
SuharkovMP
Возьмите как аналог следующую свою функцию
А еще у вас не учтено правило 50 ходов.
andyshark1974
И еще - недостаточно материала для мата :-)