Дело было вечером, делать было нечего. Решил запрограммировать шахматы на двоих. Делать их будем на Pygame, а как — расскажу далее. Надо сказать, что я в Python и Pygame тот ещё чайник, так что код и мои пояснения далеки от идеала. Давайте приступим к разработке.
Начнём со стандартных действий вроде импортирования нужных библиотек, создания окна и игрового цикла
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=1 while game: #цикл активен пока игра не закрыта for e in event.get(): if e.type==QUIT: game=0 #при нажатии на крестик игра закрывается display.update() clock.tick(60)
Теперь нарисуем доску
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 DrawBg(): #функция, рисующая доску pygame.draw.rect(wind, (181, 136, 99), (0, 0, 640, 640)) #рисуем большую черную клетку на весь экран for R in RectList: pygame.draw.rect(wind, ((240, 217, 181)), R) #на большой черной клетке рисуем маленькие белые клетки из нашего списка
Пора создать матрицу, в которой будут храниться данные о фигурах. Каждой фигуре надо дать свое значение. У меня они следующие:
Пустая клетка — .
Король-K (King)
Королева-Q (Queen)
Ладья-R (Rock)
Конь-H (Horse)
Слон-B (Bishop)
Белая пешка-P (Pawn)
Черная пешка-p (Pawn)
Надо сказать, что отличия между белой и черной пешкой обоснованы их различием в атаке и ходах. Белые атакуют и ходят вверх, а черные — вниз.
Для обозначения цвета примем 0 за белый, 1 — за черный.
Итак, наша матрица:
Board=[ ['R1','H1','B1','Q1','K1','B1','H1','R1'], ['p1','p1','p1','p1','p1','p1','p1','p1'], ['.','.','.','.','.','.','.','.'], ['.','.','.','.','.','.','.','.'], ['.','.','.','.','.','.','.','.'], ['.','.','.','.','.','.','.','.'], ['P0','P0','P0','P0','P0','P0','P0','P0'], ['R0','H0','B0','Q0','K0','B0','H0','R0']]
Давайте нарисуем и фигуры. Для этого я добавил картинки с нужными фигурами в папку с игрой. Картинки называются в соответствии с зашифровкой фигуры в матрице. Например: черный конь — H1.png, белая пешка — P0.png, Черная королева — Q1.png и т.д.
def DrawPieces(): y=0 for Brd in Board: x=0 for B in Brd: if Board[y][x]!='.': #если рассматриваемая клетка не пуста wind.blit(transform.scale(pygame.image.load(Board[y][x]+'.png'),(70,70)),(5+x*80,5+y*80))#добавить картинку нужной фигуры в соответствующие координаты x+=1 y+=1
Теперь перейдём к технической части кода. Начать я решил с такой важной вещи как проверка на шах. Ведь по правилам ход, после которого король ходившего находится под шахом, недопустим. Для этого создадим словарь AttackDict.
AttackDict={'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], 'H':[[1,2],[2,1],[-1,-2],[-2,-1],[-1,2],[-2,1],[1,-2],[2,-1],0], 'P':[[-1,-1],[1,-1],0], 'p':[[-1,1],[1,1],0], 'K':[[1,1],[-1,-1],[1,-1],[-1,1],[0,1],[1,0],[0,-1],[-1,0],0] }
Объясняю: каждой фигуре соответствует список. Все значения до последнего — показывают направление атаки, то есть куда идет смещение по X и Y относительно фигуры.
Последние значение — 0 или 1. 1 означает, что фигура атакует во всю длину поля как королева или ладья. 0 означает что атака «одинарная» как у короля, пешки, коня.
Теперь создадим функцию, которая проверит все фигуры, клетки которые они бьют и поймёт, под шахом ли король.
def CheckShah(B_W): #аргумент B_W принимает значение 0 или 1. 0-Если интересует шах белого короля, 1-если черного y=0 for Brd in Board: #проверка каждой строки x=0 for B in Brd: #проверка каждой клетки строки, теперь B-проверяемая фигура if B!='.': #если клетка не пуста if B[1]!=B_W: #если найденная фигура противоположного цвета с проверяемым королём и, соответственно, может его атаковать for shift in AttackDict[B[0]][0:-1]: #shift-направление атаки, числа показывающие сдвиг по X и Y pos=[x,y] #позиция найденной фигуры for i in range(AttackDict[B[0]][-1]*6+1): #если атака во всё поле, то цикл повторится 7 раз, иначе - 1 раз. pos[0]+=shift[0] pos[1]+=shift[1]#сместим рассматриваемую позицию в соответствии с shift if pos[0]>7 or pos[0]<0 or pos[1]>7 or pos[1]<0: break #если X или Y рассматриваемой позиции выходит за пределы поля, то остановить проверку этого направления атаки if Board[pos[1]][pos[0]]!='.': if Board[pos[1]][pos[0]]!='K'+B_W: break #если поле не пустое и на нём не стоит вражеский король, то остановить проверку этого направления атаки else: return True #если король в клетке всё же есть - вернуть True. Король действительно под шахом x+=1 y+=1 return False #если шах так и не был обнаружен - вернуть False
Далее можем добавить функцию, которая определит какие ходы доступны выбранной фигуре
def ShowVariants(x,y): #x,y-координаты фигуры, для которой нужно определить ходы global Variants Variants=[] #список вариантов ходов B=Board[y][x] #B-фигура, для которой нужно определить ходы for shift in AttackDict[B[0]][0:-1]:#уже знакомый shift-сдвиг pos=[x,y] #а так же знакомая позиция фигуры-pos for i in range(AttackDict[B[0]][-1]*6+1): #если атака во всё поле, то цикл повторится 7 раз, иначе-1 раз. pos[0]+=shift[0] pos[1]+=shift[1]#опять смещаем позицию с помощью shift if pos[0]>7 or pos[0]<0 or pos[1]>7 or pos[1]<0: break #если X или Y рассматриваемой позиции выходит за пределы поля, то остановить проверку этого направления if Board[pos[1]][pos[0]]!='.': #если клетка не пуста if Board[pos[1]][pos[0]][1]!=Board[y][x][1]: Variants.append([pos[0],pos[1]]) #если клетку занимает вражеская фигура то добавить её как вариант хода else: break #если же клетку заняла дружеская фигура, то остановить эту линию ходов elif B[0]!='p' and B[0]!='P': #если клетка пуста, а рассматриваемая фигура не пешка, то добавить её как вариант хода. (Пешка не может ходить на пустую клетку по диагонали) Variants.append([pos[0],pos[1]]) if B[0]=='P': #если рассматриваемая фигура-белая пешка, то добавим стандартные ходы пешек, производимые без взятия pos=[x,y] for i in range((y==6)+1): #если пешка на 6-ой линии то цикл повторится 2 раза, иначе-1. (По правилам пешки могут ходить на две клетки, если они на "родной линии") pos[1]-=1 if pos[1]<0: break #если вышли за пределы-стоп if Board[pos[1]][pos[0]]!='.':break #если клетка впереди не пуста, а занята - тоже стоп Variants.append([pos[0],pos[1]])#если цикл ещё не остановлен, то добавим рассматриваемую клетку как вариант хода if B[0]=='p':#все тоже самое для черной пешки pos=[x,y] for i in range((y==1)+1): pos[1]+=1 if pos[1]>7: break if Board[pos[1]][pos[0]]!='.':break Variants.append([pos[0],pos[1]]) #Теперь дело за малым - откинуть все ходы, которые ставят своего короля под шах ForDeletion=[] #список вариантов на удаление Board[y][x]='.' #временно уберем рассматриваемую фигуру со стола for V in Variants: #переберем все варианты remember=Board[V[1]][V[0]] #запоминаем клетку, на которую сейчас поставим фигуру Board[V[1]][V[0]]=B #ставим фигуру на это место if CheckShah(B[1]): ForDeletion.append(V) #если король под шахом - добавим этот вариант в список на удаление Board[V[1]][V[0]]=remember #возвращаем клетку которую запомнили Board[y][x]=B #вернём рассматриваемую фигуру на стол for Del in ForDeletion: #удалим все недопустимые варианты Variants.remove(Del)
Самое сложное позади. Можно добавить проверку на мат или пат
def CheckCheckMate(B_W): #аргумент B_W - как обычно, 0 - интересует мат/пат белых, 1 - черных global Variants y=0 for Brd in Board: #проверка каждой строки x=0 for B in Brd: #проверка каждого элемента строки if B[-1]==B_W: #если найдена фигура нужного цвета то проверить, есть ли для неё хоть один вариант хода. Если да - вернуть 0, мата или пата нет ShowVariants(x,y) if len(Variants)>0:Variants=[];return 0 x+=1 y+=1 #если дошли до этой строки, то это значит, что ни одна фигура нужного цвета не может сделать ход. Это означает, что поставлен мат или пат if CheckShah(B_W): Variants=[];return 1 #король под шахом - значит мат, возвращаем 1 else: Variants=[];return 2 #король не под шахом - пат, возвращаем 2 #обратите внимание, что перед тем, как вернуть значение, необходимо очистить список Variants, чтобы избежать багов
Наконец добавим возможность ходить и проверку на мат или пат. Для понимания отмечу, что игрок фигуры будет «перетаскивать». То есть выбирать фигуру нажатием, а отпуская кнопку — переставлять.
Теперь игровой цикл выглядит как то так:
Variants=[] DrawBg() DrawPieces() Turn=0 game=1 while game: for e in event.get(): if e.type==QUIT: game=0 if e.type==pg.MOUSEBUTTONDOWN and e.button==1: #если нажата ЛКМ x,y=(e.pos) #x,y-положение мыши x,y=math.floor(x/80),math.floor(y/80) #поделив x,y на 80 получаем клетку, на которую нажал игрок if Board[y][x]!='.': #если она не пуста if Board[y][x][1]==str(Turn): #и равна переменной Turn - очередь. Turn меняется каждый ход ShowVariants(x,y) #получаем список доступных ходов remember=[x,y] #запомним клетку, на которую нажали for V in Variants: pygame.draw.circle(wind, (200,200,200), (V[0]*80+40, V[1]*80+40), 10) #отрисовка кружочков показывающих, куда можно сходить if e.type==pg.MOUSEBUTTONUP and e.button==1: #если ОТжата ЛКМ x,y=(e.pos) x,y=math.floor(x/80),math.floor(y/80) #получаем клетку, в которой находится мышка if Variants.count([x,y]): #если эта клетка есть в списке возможных ходов Board[y][x]=Board[remember[1]][remember[0]] #заменяем выбранную клетку на ту, что запомнили при нажатии Board[remember[1]][remember[0]]='.' #клетку, с которой ушли, оставляем пустой Turn=1-Turn #очередь меняется с 0 на 1 или наоборот #после смены очереди надо проверить наличие мата или пата check=CheckCheckMate(str(Turn)) #check примет 1 если объявлен мат, 2 - если пат, 0 - в ином случае if check==1: #если мат DrawBg()#рисуем доску напоследок DrawPieces() if Turn==0:#и в зависимости от того, чья очередь, объявляем победителя wind.blit(pygame.font.SysFont(None,30).render('BLACK WON', False,(30, 30, 30)),(260,310)) if Turn==1: wind.blit(pygame.font.SysFont(None,30).render('WHITE WON', False,(30, 30, 30)),(260,310)) if check==2: #если пат, то объявляем ничью wind.blit(pygame.font.SysFont(None,30).render('DRAW', False,(30, 30, 30)),(290,310)) Variants=[] if check==0: #доска отрисуется только если не объявлен мат или пат DrawBg() DrawPieces() Variants=[] #очистим список вариантов, во избежание багов display.update() clock.tick(60)
На этом этапе уже можно играть, но не хватает 2 важные вещи. Во-первых, пешка, при достижении противоположного края доски должна превращаться в коня, слона, ладью или ферзя на усмотрение игрока. Во-вторых, не хватает рокировки. Начнём с превращения пешек.
для этого добавим в игровой цикл следующие строки:
if Board[0].count('P0') and Turn==1: #если в нулевой строке найдена белая пешка Turn=-1 #то временно меняем очередь на -1. Это значит, что белые выбирают фигуру для замены пешки PawnX=Board[0].index('P0') #зададим переменной PawnX значение x, где располагается белая пешка wind.blit(transform.scale(image.load('Q0.png'),(40,40)),(PawnX*80,0)) wind.blit(transform.scale(image.load('R0.png'),(40,40)),(40+PawnX*80,0)) #нарисуем фигуры, на которые нужно будет нажать игроку wind.blit(transform.scale(image.load('B0.png'),(40,40)),(PawnX*80,40)) wind.blit(transform.scale(image.load('H0.png'),(40,40)),(40+PawnX*80,40))
Теперь добавим в событие с нажатием мыши случай, когда Turn=-1
if e.type==pg.MOUSEBUTTONDOWN and e.button==1: #если нажата ЛКМ if Turn==-1: x,y=(e.pos) #координаты мыши if PawnX+1>x/80>=PawnX and y<80: #если нажали на клетку где стоит пешка x=x%80 if 40>x>=0 and 40>y>=0:Board[0][PawnX]='Q0' #в зависимости от того, в какой угол нажал игрок (королева,ладья,слон и конь нарисованы по углам клетки с пешкой), превратим его пешку в соответствующую фигуру elif 40>x>=0 and 80>y>=40:Board[0][PawnX]='B0' elif 80>x>=40 and 40>y>=0:Board[0][PawnX]='R0' elif 80>x>=40 and 80>y>=40:Board[0][PawnX]='H0' Turn=1 #вернём черным ход DrawBg() DrawPieces() #отрисуем доску check=CheckCheckMate('1') if check==1: wind.blit(pygame.font.SysFont(None,30).render('WHITE WON', False,(30, 30, 30)),(260,310)) #и напоследок проверим наличие мата или пата if check==2: wind.blit(pygame.font.SysFont(None,30).render('DRAW', False,(30, 30, 30)),(290,310))
Аналогичные строки напишем для чёрных. Здесь смотреть не на что.
Ах да, чуть не забыл добавить в if срабатывающий при поднятии ЛКМ проверку на то что Turn не равен -1 или -2 (-2 — значение при превращении черной пешки)
if e.type==pg.MOUSEBUTTONUP and e.button==1 and Turn!=-1 and Turn!=-2: #если ОТжата ЛКМ
Последний рывок: добавим рокировку. Давайте напомню тем, кто не знает или забыл. Рокировка — ход, заключающийся в горизонтальном перемещении короля в сторону ладьи своего цвета на 2 клетки и последующем перемещении ладьи на соседнюю с королём клетку по другую сторону от короля. Простыми словами вы переставляете ладью и короля одновременно. Рокировка невозможна если ладья или король уже делали ход, если между ладьёй и королём есть фигуры. Так же она невозможна если король под шахом или во время совершения рокировки пройдет через клетку, находящуюся под ударом.
Начнём как обычно с добавления рокировки для белых, а аналогичные строки для чёрных показывать необходимости нет. Итак, я добавил переменную CastlingL0 и CastlingR0 — они отвечают за возможность совершения рокировки с левой белой ладьёй и правой белой ладьёй соответственно (в названии переменных для чёрных заменю 0 на 1)
Добавим в if срабатывающий при отжатии мыши следующие строки:
if Board[7][0]!='R0': castlingL0=False #если левая ладья не на месте, запретить делать с ней рокировку if Board[7][7]!='R0': castlingR0=False #если правая ладья не на месте, запретить делать с ней рокировку if Board[7][4]!='K0': castlingL0=False;castlingR0=False #если король не на месте, запретить делать рокировку впринципе
Теперь добавим в самый конец функции, находящей варианты хода для фигур следующие строчки:
if Board[y][x]=='K0': #если рассматриваем ходы для белого короля global castlingL0, castlingR0 if Board[7][0:5]==['R0','.','.','.','K0'] and castlingL0: #если между левой ладьёй и королём пусто, а рокировка с левой ладьёй не запрещена Board[7][2],Board[7][3]='K0','K0' #временно поставим два короля в клетки через которые пройдёт король if CheckShah('0')==0: #если эти короли не получают шаха, то это значит, что все условия для рокировки есть и можно добавлять ход-рокировку Variants.append([2,7]) Board[7][2],Board[7][3]='.','.' #уберём временных королей if Board[7][4:8]==['K0','.','.','R0'] and castlingR0: #все тоже самое для рокировки с правой ладьёй Board[7][5],Board[7][6]='K0','K0' if CheckShah('0')==0: Variants.append([6,7]) Board[7][5],Board[7][6]='.','.'
Теперь король уже может ходить на 2 клетки при соблюдении всех условий, осталось лишь начать смещать ладью. Следующие строки кода мы добавим в if срабатывающий при отжатии мыши.
Board[y][x]=Board[remember[1]][remember[0]] Board[remember[1]][remember[0]]='.' #старые строчки кода которые перемещают фигуру во время совершения хода if remember==[4,7] and Board[y][x]=='K0': #если фигура, которую мы переместили, была белым королём, находящимся в клетке 4,7 (стандартная позиция короля) if [x,y]==[2,7]: Board[7][0]='.';Board[7][3]='R0' #если этот король перешёл в клетку 2,7, то переставить левую ладью if [x,y]==[6,7]: Board[7][7]='.';Board[7][5]='R0' #если этот король перешёл в клетку 6,7, то переставить правую ладью
Всё! Осталось сделать то же для чёрных и игра готова!
Ссылка на Яндекс Диск с игрой.
Переходите по ссылке >>> Нажимайте на кнопку скачать всё >>> сохраняйте Chess.zip. >>> Открывайте его >>> перетаскивайте папку Chess себе на рабочий стол или в любое место проводника >>> открываете эту папку >>> открываете Chess.py >>> можно играть.
Просьба, оставлять в комментариях критику или информацию об ошибках если таковые имеются.
ссылка на оригинал статьи https://habr.com/ru/articles/836214/
Добавить комментарий