Пишем шахматы на двоих на Pygame

от автора

Дело было вечером, делать было нечего. Решил запрограммировать шахматы на двоих. Делать их будем на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *