Как стать автором
Обновить

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

Уровень сложностиСредний
Время на прочтение10 мин
Количество просмотров6.8K

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

Просьба, оставлять в комментариях критику или информацию об ошибках если таковые имеются.

Теги:
Хабы:
Всего голосов 3: ↑3 и ↓0+3
Комментарии16

Публикации

Работа

Data Scientist
42 вакансии

Ближайшие события