Как написать шашки на python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
#Python 3.2.3 / IDLE 3.3.0
#Игра "Шашки". Автор: Я
#08.08.2012-22.04.2013
from tkinter import *
import random
import time
import copy
 
gl_okno=Tk()#создаём окно
gl_okno.title('Шашки от YARIKO')#заголовок окна
doska=Canvas(gl_okno, width=800,height=800,bg='#FFFFFF')
doska.pack()
 
n2_spisok=()#конечный список ходов компьютера
ur=3#количество предсказываемых компьютером ходов
k_rez=0#!!!
o_rez=0
poz1_x=-1#клетка не задана
f_hi=True#определение хода игрока(да)
 
def izobrazheniya_peshek():#загружаем изображения пешек
    global peshki
    i1=PhotoImage(file="res\1b.gif")
    i2=PhotoImage(file="res\1bk.gif")
    i3=PhotoImage(file="res\1h.gif")
    i4=PhotoImage(file="res\1hk.gif")
    peshki=[0,i1,i2,i3,i4]
 
def novaya_igra():#начинаем новую игру
    global pole
    pole=[[0,3,0,3,0,3,0,3],
          [3,0,3,0,3,0,3,0],
          [0,3,0,3,0,3,0,3],
          [0,0,0,0,0,0,0,0],
          [0,0,0,0,0,0,0,0],
          [1,0,1,0,1,0,1,0],
          [0,1,0,1,0,1,0,1],
          [1,0,1,0,1,0,1,0]]
 
def vivod(x_poz_1,y_poz_1,x_poz_2,y_poz_2):#рисуем игровое поле
    global peshki
    global pole
    global kr_ramka,zel_ramka
    k=100
    x=0
    doska.delete('all')
    kr_ramka=doska.create_rectangle(-5, -5, -5, -5,outline="red",width=5)
    zel_ramka=doska.create_rectangle(-5, -5, -5, -5,outline="green",width=5)
 
    while x<8*k:#рисуем доску
        y=1*k
        while y<8*k:
            doska.create_rectangle(x, y, x+k, y+k,fill="black")
            y+=2*k
        x+=2*k
    x=1*k
    while x<8*k:#рисуем доску
        y=0
        while y<8*k:
            doska.create_rectangle(x, y, x+k, y+k,fill="black")
            y+=2*k
        x+=2*k
    
    for y in range(8):#рисуем стоячие пешки
        for x in range(8):
            z=pole[y][x]
            if z:  
                if (x_poz_1,y_poz_1)!=(x,y):#стоячие пешки?
                    doska.create_image(x*k,y*k, anchor=NW, image=peshki[z])
    #рисуем активную пешку         
    z=pole[y_poz_1][x_poz_1]
    if z:#???
        doska.create_image(x_poz_1*k,y_poz_1*k, anchor=NW, image=peshki[z],tag='ani')
    #вычисление коэф. для анимации
    kx = 1 if x_poz_1<x_poz_2 else -1
    ky = 1 if y_poz_1<y_poz_2 else -1
    for i in range(abs(x_poz_1-x_poz_2)):#анимация перемещения пешки
        for ii in range(33):
            doska.move('ani',0.03*k*kx,0.03*k*ky)
            doska.update()#обновление
            time.sleep(0.01)
 
def soobsenie(s):
    global f_hi
    z='Игра завершена'
    if s==1:
        i=messagebox.askyesno(title=z, message='Вы победили!nНажми "Да" что бы начать заново.',icon='info')
    if s==2:
        i=messagebox.askyesno(title=z, message='Вы проиграли!nНажми "Да" что бы начать заново.',icon='info')
    if s==3:
        i=messagebox.askyesno(title=z, message='Ходов больше нет.nНажми "Да" что бы начать заново.',icon='info')
    if i:
        novaya_igra()
        vivod(-1,-1,-1,-1)#рисуем игровое поле
        f_hi=True#ход игрока доступен
 
def pozici_1(event):#выбор клетки для хода 1
    x,y=(event.x)//100,(event.y)//100#вычисляем координаты клетки
    doska.coords(zel_ramka,x*100,y*100,x*100+100,y*100+100)#рамка в выбранной клетке
 
def pozici_2(event):#выбор клетки для хода 2
    global poz1_x,poz1_y,poz2_x,poz2_y
    global f_hi
    x,y=(event.x)//100,(event.y)//100#вычисляем координаты клетки
    if pole[y][x]==1 or pole[y][x]==2:#проверяем пешку игрока в выбранной клетке
        doska.coords(kr_ramka,x*100,y*100,x*100+100,y*100+100)#рамка в выбранной клетке
        poz1_x,poz1_y=x,y
    else:
        if poz1_x!=-1:#клетка выбрана
            poz2_x,poz2_y=x,y
            if f_hi:#ход игрока?
                hod_igroka()
                if not(f_hi):
                    time.sleep(0.5)
                    hod_kompjutera()#передаём ход компьютеру
                    #gl_okno.after(500,hod_kompjutera(0))#!!!#передаём ход компьютеру
            poz1_x=-1#клетка не выбрана
            doska.coords(kr_ramka,-5,-5,-5,-5)#рамка вне поля              
     
def hod_kompjutera():#!!!
    global f_hi
    global n2_spisok
    proverka_hk(1,(),[])
    if n2_spisok:#проверяем наличие доступных ходов
        kh=len(n2_spisok)#количество ходов
        th=random.randint(0,kh-1)#случайный ход
        dh=len(n2_spisok[th])#длина хода
        for h in n2_spisok:#!!!для отладки!!!
            h=h#!!!для отладки!!!
        for i in range(dh-1):
            #выполняем ход
            spisok=hod(1,n2_spisok[th][i][0],n2_spisok[th][i][1],n2_spisok[th][1+i][0],n2_spisok[th][1+i][1])
        n2_spisok=[]#очищаем список ходов
        f_hi=True#ход игрока доступен
 
    #определяем победителя 
    s_k,s_i=skan()
    if not(s_i):
            soobsenie(2)
    elif not(s_k):
            soobsenie(1)
    elif f_hi and not(spisok_hi()):
            soobsenie(3)
    elif not(f_hi) and not(spisok_hk()):
            soobsenie(3)
 
def spisok_hk():#составляем список ходов компьютера
    spisok=prosmotr_hodov_k1([])#здесь проверяем обязательные ходы
    if not(spisok):
        spisok=prosmotr_hodov_k2([])#здесь проверяем оставшиеся ходы
    return spisok
 
def proverka_hk(tur,n_spisok,spisok):#!!!
    global pole
    global n2_spisok
    global l_rez,k_rez,o_rez
    if not(spisok):#если список ходов пустой...
        spisok=spisok_hk()#заполняем
 
    if spisok:
        k_pole=copy.deepcopy(pole)#копируем поле
        for ((poz1_x,poz1_y),(poz2_x,poz2_y)) in spisok:#проходим все ходы по списку
            t_spisok=hod(0,poz1_x,poz1_y,poz2_x,poz2_y)
            if t_spisok:#если существует ещё ход
                proverka_hk(tur,(n_spisok+((poz1_x,poz1_y),)),t_spisok)
            else:
                proverka_hi(tur,[])
                if tur==1:
                    t_rez=o_rez/k_rez
                    if not(n2_spisok):#записыаем если пустой
                        n2_spisok=(n_spisok+((poz1_x,poz1_y),(poz2_x,poz2_y)),)
                        l_rez=t_rez#сохряняем наилучший результат
                    else:
                        if t_rez==l_rez:
                            n2_spisok=n2_spisok+(n_spisok+((poz1_x,poz1_y),(poz2_x,poz2_y)),)
                        if t_rez>l_rez:
                            n2_spisok=()
                            n2_spisok=(n_spisok+((poz1_x,poz1_y),(poz2_x,poz2_y)),)
                            l_rez=t_rez#сохряняем наилучший результат
                    o_rez=0
                    k_rez=0
 
            pole=copy.deepcopy(k_pole)#возвращаем поле
    else:#???
        s_k,s_i=skan()#подсчёт результата хода
        o_rez+=(s_k-s_i)
        k_rez+=1
 
def spisok_hi():#составляем список ходов игрока
    spisok=prosmotr_hodov_i1([])#здесь проверяем обязательные ходы
    if not(spisok):
        spisok=prosmotr_hodov_i2([])#здесь проверяем оставшиеся ходы
    return spisok
    
def proverka_hi(tur,spisok):
    global pole,k_rez,o_rez
    global ur
    if not(spisok):
        spisok=spisok_hi()
 
    if spisok:#проверяем наличие доступных ходов
        k_pole=copy.deepcopy(pole)#копируем поле
        for ((poz1_x,poz1_y),(poz2_x,poz2_y)) in spisok:                    
            t_spisok=hod(0,poz1_x,poz1_y,poz2_x,poz2_y)
            if t_spisok:#если существует ещё ход
                proverka_hi(tur,t_spisok)
            else:
                if tur<ur:
                    proverka_hk(tur+1,(),[])
                else:
                    s_k,s_i=skan()#подсчёт результата хода
                    o_rez+=(s_k-s_i)
                    k_rez+=1
 
            pole=copy.deepcopy(k_pole)#возвращаем поле
    else:#доступных ходов нет
        s_k,s_i=skan()#подсчёт результата хода
        o_rez+=(s_k-s_i)
        k_rez+=1
 
def skan():#подсчёт пешек на поле
    global pole
    s_i=0
    s_k=0
    for i in range(8):
        for ii in pole[i]:
            if ii==1:s_i+=1
            if ii==2:s_i+=3
            if ii==3:s_k+=1
            if ii==4:s_k+=3
    return s_k,s_i
 
def hod_igroka():
    global poz1_x,poz1_y,poz2_x,poz2_y
    global f_hi
    f_hi=False#считаем ход игрока выполненным
    spisok=spisok_hi()
    if spisok:
        if ((poz1_x,poz1_y),(poz2_x,poz2_y)) in spisok:#проверяем ход на соответствие правилам игры
            t_spisok=hod(1,poz1_x,poz1_y,poz2_x,poz2_y)#если всё хорошо, делаем ход            
            if t_spisok:#если есть ещё ход той же пешкой
                f_hi=True#считаем ход игрока невыполненным
        else:
            f_hi=True#считаем ход игрока невыполненным
    doska.update()#!!!обновление
 
def hod(f,poz1_x,poz1_y,poz2_x,poz2_y):
    global pole
    if f:vivod(poz1_x,poz1_y,poz2_x,poz2_y)#рисуем игровое поле
    #превращение
    if poz2_y==0 and pole[poz1_y][poz1_x]==1:
        pole[poz1_y][poz1_x]=2
    #превращение
    if poz2_y==7 and pole[poz1_y][poz1_x]==3:
        pole[poz1_y][poz1_x]=4
    #делаем ход           
    pole[poz2_y][poz2_x]=pole[poz1_y][poz1_x]
    pole[poz1_y][poz1_x]=0
 
    #рубим пешку игрока
    kx=ky=1
    if poz1_x<poz2_x:kx=-1
    if poz1_y<poz2_y:ky=-1
    x_poz,y_poz=poz2_x,poz2_y
    while (poz1_x!=x_poz) or (poz1_y!=y_poz):
        x_poz+=kx
        y_poz+=ky
        if pole[y_poz][x_poz]!=0:
            pole[y_poz][x_poz]=0
            if f:vivod(-1,-1,-1,-1)#рисуем игровое поле
            #проверяем ход той же пешкой...
            if pole[poz2_y][poz2_x]==3 or pole[poz2_y][poz2_x]==4:#...компьютера
                return prosmotr_hodov_k1p([],poz2_x,poz2_y)#возвращаем список доступных ходов
            elif pole[poz2_y][poz2_x]==1 or pole[poz2_y][poz2_x]==2:#...игрока
                return prosmotr_hodov_i1p([],poz2_x,poz2_y)#возвращаем список доступных ходов
    if f:vivod(poz1_x,poz1_y,poz2_x,poz2_y)#рисуем игровое поле
 
def prosmotr_hodov_k1(spisok):#проверка наличия обязательных ходов
    for y in range(8):#сканируем всё поле
        for x in range(8):
            spisok=prosmotr_hodov_k1p(spisok,x,y)
    return spisok
 
def prosmotr_hodov_k1p(spisok,x,y):
    if pole[y][x]==3:#пешка
        for ix,iy in (-1,-1),(-1,1),(1,-1),(1,1):
            if 0<=y+iy+iy<=7 and 0<=x+ix+ix<=7:
                if pole[y+iy][x+ix]==1 or pole[y+iy][x+ix]==2:
                    if pole[y+iy+iy][x+ix+ix]==0:
                        spisok.append(((x,y),(x+ix+ix,y+iy+iy)))#запись хода в конец списка
    if pole[y][x]==4:#пешка с короной
        for ix,iy in (-1,-1),(-1,1),(1,-1),(1,1):
            osh=0#определение правильности хода
            for i in  range(1,8):
                if 0<=y+iy*i<=7 and 0<=x+ix*i<=7:
                    if osh==1:
                        spisok.append(((x,y),(x+ix*i,y+iy*i)))#запись хода в конец списка
                    if pole[y+iy*i][x+ix*i]==1 or pole[y+iy*i][x+ix*i]==2:
                        osh+=1
                    if pole[y+iy*i][x+ix*i]==3 or pole[y+iy*i][x+ix*i]==4 or osh==2:
                        if osh>0:spisok.pop()#удаление хода из списка
                        break
    return spisok
 
def prosmotr_hodov_k2(spisok):#проверка наличия остальных ходов
    for y in range(8):#сканируем всё поле
        for x in range(8):
            if pole[y][x]==3:#пешка
                for ix,iy in (-1,1),(1,1):
                    if 0<=y+iy<=7 and 0<=x+ix<=7:
                        if pole[y+iy][x+ix]==0:
                            spisok.append(((x,y),(x+ix,y+iy)))#запись хода в конец списка
                        if pole[y+iy][x+ix]==1 or pole[y+iy][x+ix]==2:
                            if 0<=y+iy*2<=7 and 0<=x+ix*2<=7:
                                if pole[y+iy*2][x+ix*2]==0:
                                    spisok.append(((x,y),(x+ix*2,y+iy*2)))#запись хода в конец списка                  
            if pole[y][x]==4:#пешка с короной
                for ix,iy in (-1,-1),(-1,1),(1,-1),(1,1):
                    osh=0#определение правильности хода
                    for i in range(1,8):
                        if 0<=y+iy*i<=7 and 0<=x+ix*i<=7:
                            if pole[y+iy*i][x+ix*i]==0:
                                spisok.append(((x,y),(x+ix*i,y+iy*i)))#запись хода в конец списка
                            if pole[y+iy*i][x+ix*i]==1 or pole[y+iy*i][x+ix*i]==2:
                                osh+=1
                            if pole[y+iy*i][x+ix*i]==3 or pole[y+iy*i][x+ix*i]==4 or osh==2:
                                break
    return spisok
 
def prosmotr_hodov_i1(spisok):#проверка наличия обязательных ходов
    spisok=[]#список ходов
    for y in range(8):#сканируем всё поле
        for x in range(8):
            spisok=prosmotr_hodov_i1p(spisok,x,y)
    return spisok
 
def prosmotr_hodov_i1p(spisok,x,y):
    if pole[y][x]==1:#пешка
        for ix,iy in (-1,-1),(-1,1),(1,-1),(1,1):
            if 0<=y+iy+iy<=7 and 0<=x+ix+ix<=7:
                if pole[y+iy][x+ix]==3 or pole[y+iy][x+ix]==4:
                    if pole[y+iy+iy][x+ix+ix]==0:
                        spisok.append(((x,y),(x+ix+ix,y+iy+iy)))#запись хода в конец списка
    if pole[y][x]==2:#пешка с короной
        for ix,iy in (-1,-1),(-1,1),(1,-1),(1,1):
            osh=0#определение правильности хода
            for i in  range(1,8):
                if 0<=y+iy*i<=7 and 0<=x+ix*i<=7:
                    if osh==1:
                        spisok.append(((x,y),(x+ix*i,y+iy*i)))#запись хода в конец списка
                    if pole[y+iy*i][x+ix*i]==3 or pole[y+iy*i][x+ix*i]==4:
                        osh+=1
                    if pole[y+iy*i][x+ix*i]==1 or pole[y+iy*i][x+ix*i]==2 or osh==2:
                        if osh>0:spisok.pop()#удаление хода из списка
                        break
    return spisok
 
def prosmotr_hodov_i2(spisok):#проверка наличия остальных ходов
    for y in range(8):#сканируем всё поле
        for x in range(8):
            if pole[y][x]==1:#пешка
                for ix,iy in (-1,-1),(1,-1):
                    if 0<=y+iy<=7 and 0<=x+ix<=7:
                        if pole[y+iy][x+ix]==0:
                            spisok.append(((x,y),(x+ix,y+iy)))#запись хода в конец списка
                        if pole[y+iy][x+ix]==3 or pole[y+iy][x+ix]==4:
                            if 0<=y+iy*2<=7 and 0<=x+ix*2<=7:
                                if pole[y+iy*2][x+ix*2]==0:
                                    spisok.append(((x,y),(x+ix*2,y+iy*2)))#запись хода в конец списка                  
            if pole[y][x]==2:#пешка с короной
                for ix,iy in (-1,-1),(-1,1),(1,-1),(1,1):
                    osh=0#определение правильности хода
                    for i in range(1,8):
                        if 0<=y+iy*i<=7 and 0<=x+ix*i<=7:
                            if pole[y+iy*i][x+ix*i]==0:
                                spisok.append(((x,y),(x+ix*i,y+iy*i)))#запись хода в конец списка
                            if pole[y+iy*i][x+ix*i]==3 or pole[y+iy*i][x+ix*i]==4:
                                osh+=1
                            if pole[y+iy*i][x+ix*i]==1 or pole[y+iy*i][x+ix*i]==2 or osh==2:
                                break
    return spisok
 
izobrazheniya_peshek()#здесь загружаем изображения пешек
novaya_igra()#начинаем новую игру
vivod(-1,-1,-1,-1)#рисуем игровое поле
doska.bind("<Motion>", pozici_1)#движение мышки по полю
doska.bind("<Button-1>", pozici_2)#нажатие левой кнопки
 
mainloop()

Welcome to CodeReview! I’ll start by saying thank you for posting a somewhat-complex package of code. At first blush, your code appears to be written in the generally-accepted style and appears well organized and somewhat documented.

Functionality

Before I actually review the code, I’ll point out that I tried to play the game. Here’s my experience:

Your color is: ○
     1 2 3 4 5 6 7 8

A   | |●| |●| |●| |●|   A
B   |●| |●| |●| |●| |   B
C   | |●| |●| |●| |●|   C
D   | | | | | | | | |   D
E   | | | | | | | | |   E
F   |○| |○| |○| |○| |   F
G   | |○| |○| |○| |○|   G
H   |○| |○| |○| |○| |   H

     1 2 3 4 5 6 7 8
Choose you checkerf1
Enter coordinatese1
Choose you checkerf1
Enter coordinatese2
Test Error
Your color is: ●
     1 2 3 4 5 6 7 8

A   | |●| |●| |●| |●|   A
B   |●| |●| |●| |●| |   B
C   | |●| |●| |●| |●|   C
D   | | | | | | | | |   D
E   | |○| | | | | | |   E
F   | | |○| |○| |○| |   F
G   | |○| |○| |○| |○|   G
H   |○| |○| |○| |○| |   H

     1 2 3 4 5 6 7 8

So I have some comments on the actual operation of the game :-).

Playability

First, the game board does not color black/white squares. This makes it harder to play, since the alternating colors help with distinguishing different rows/columns, as well as providing a visual crutch for understanding move possibilities.

Next, the coordinate system is perfectly functional, but not at all intuitive. I’d suggest you consider some alternatives, like labelling the individual checkers with letters A-L instead of circles. Similarly, you might consider enumerating the possible destinations for movement. Either explicitly (redraw the board with 1..4 markers) or implicitly (draw a compass rose with 1..4 on it alongside the board).

Input/Output

The prompt needs to include a space at the end. Otherwise, you get what I got:

Choose you checkerf1

There is no indication of a help system. If someone doesn’t know how to play checkers, or doesn’t remember, how do they get help? Where are the rules of play?

If I enter a bad move, as I did, there’s no rebuff message. Instead, just a new prompt. You should explain that my move is invalid, and either print a rule summary («You must move 1 space diagonally, or jump over an enemy piece diagonally») or print an enumeration of valid moves for the piece.

I don’t know what a Test Error is. But I’m pretty sure that it shouldn’t be appearing during gameplay.

Naming

I have two comments on your file naming. First, instead of game_loop.py you should have named the file checkers.py or main.py (or __main__.py). Because there’s nothing obvious about what game this is, just looking at the file names. I mean, deck_and_cheez.py? What game did you start writing before you switched to checkers?

Second, there just isn’t that much code in these files:

aghast@laptop:~/Code/so/checkers$ wc -l *py
  312 bot.py
  692 deck_and_cheez.py
   58 game_loop.py
 1062 total

Why not just move all the code into checkers.py? This isn’t java, there’s no requirement to have a bunch of little files laying around.

game_loop.py

Structure

Everything in this file should be in a function. Possibly a different function in a different class, but definitely in a function. The standard idiom for python scripts is this:

def main():
    ... all the stuff that isn't in a function ...

if __name__ == '__main__':
    main()

Use this idiom. It makes it possible to do a bunch of things, including writing unit tests, that will benefit you. It doesn’t cost much (just one line, and some tabs), and pays off quickly.

Modularity

What do I learn from this code? (I deleted some vertical space for convenience.)

colors = ['○', '●']
deck = deck_and_cheez.Deck(random.choice(colors))
checker_pos = deck_and_cheez.CurrentChecker()
ALLIES = None
ENEMY = None

while True:
    print(f'Your color is: {deck.color}')
    deck.print_current_deck()

    if ALLIES is None:
        ALLIES = deck.color
    elif ENEMY is None:

Three things. First, is that your deck_and_cheez.Deck class is broken. Second, your deck_and_cheez.CurrentChecker class is even worse! Third, you aren’t taking a broad enough view.

class Deck

A class is supposed to be self-sufficient. If you give it the required arguments at creation time, the returned instance will stand alone.

Let’s look:

  • Naming: checkers is a «board game». A better name for Deck would be GameState or Board. In English, a deck is either a floor on a ship, or an alias for a pack of cards. Poker and Pinochle are played with a Deck, while Checkers and Chess are played with a Board.

  • From the colors = [...] variable, you don’t have a Deck.COLORS that provides this data to callers. Yet, this is a pretty important part of the Deck so why isn’t it there?

  • From the Deck(random.choice(colors)), it seems you don’t need to tell Deck what both colors are (you only pass in one color). Thus, I sense there is a second copy of the colors = [...] over in the Deck class somewhere. (In fact, it’s worse. See below.)

  • The code to set ALLIES and ENEMY is determining the value of the random choice you passed in as a parameter. And the two «constants» are only used to determine whose turn it is to play. This could be a part of Deck. It also could be implemented in code, just by writing player_turn() ; computer_turn().

Suggestions

I don’t think you need to «specify» a player color on creation of a new Deck. I think you should just randomly pick one, and make it available to users of the class:

deck = Deck()
player_color, bot_color = deck.player_colors

Once you have the colors allocated inside Deck, you can write a method that cycles the player-turn tracking without having to pass in any parameters:

deck.end_turn()

You should provide a mechanism for determining the end of the game. That could be a method on Deck or an exception raised by the turn handlers. Doing this makes the game loop clearer.

while deck.in_progress:

or

try:
    while True:
        player_turn()
        robot_turn()
except GameOver:
    pass

class CurrentChecker

This class is so low-key that I almost didn’t catch it. Your usage model is that you create an instance of the class:

checker_pos = deck_and_cheez.CurrentChecker()

and then later, during the human-player handling, you update it:

checker = input('Choose you checker').upper()

if deck.check_position(checker):
    if deck.check_checker_pos(checker):
        current_checker = checker_pos.coord(checker)

Problematically, you are calling methods on the deck class before you update the current_checker instance.

Suggestions

This class doesn’t do anything. Either delete it and just put all the functionality in the Deck class, or make it an internal class of the Deck.

Since you have most of the functionality implemented in Deck already, I suggest just deleting this class and letting the deck handle everything.

Narrow View

In your main loop, you are doing a bunch of things:

  • Tracking the current-player
  • Invoking the player or bot turn code
  • Implementing the move input mechanics
  • Looping to validate input
  • Updating the state of the Deck at each turn

To me, this says you need to work on the Deck class (see above), and also create another class or two. You need some code to handle player input mechanics and input validation logic. You need code to handle the simple gameplay mechanics.

I’d suggest creating a Player class, similar to the Bot class. Then you could just invoke the «play_turn()» method on two different objects.

The current-player problem can be solved by just calling players in sequence, as shown above.

The move mechanics and input validation are both part of the player interface. You could actually write different classes with different mechanics, and try them. Or make them play options, if you find that different people like different mechanics.

There should be no reason to update the deck state at the end of the turn. The deck should know enough about game play to update itself (it’s just to track whose turn it is…).

deck_and_cheez.py

First, what’s with the name? Why and_cheez?

class Deck

You have methods in this class that begin with __. Don’t do this.

print_current_deck

Your numbers list should just be a string.

Your loop could use enumerate to eliminate the letter_count variable.

I’d suggest just hard-coding 10 print statements. It would be about the same length and would make the output more clear:

print(f"t 1 2 3 4 5 6 7 8n")

print(f"At{'|'.join(self.deck[0])}tA")
print(f"Bt{'|'.join(self.deck[1])}tB")
...

But really, it isn’t the job of this class to communicate with the user. So instead of printing anything, just join the strings with a newline and return the resulting string:

return "n".join(f"t 1 2 3 4 5 6 7 8n",
        f"At{'|'.join(self.deck[0])}tA",
        ...)

__coordinates

Delete the ‘__’.

Rename this to express what it does: parse_user_coordinates

Use str.index to parse the input. It produces a more compact function:

row = "ABCDEFGH".index(usr_inp[0].upper())
col = "12345678".index(usr_inp[1])

return (row, col)

This will raise an exception if either input character is not matched. I think that’s a good way to return control to the code that’s driving the user interface, but you may want to catch the exception and do something different.

check_position

This is redundant with the method above. You print error messages, but it isn’t the job of this class to communicate with the user. Better, IMO, to return a value or raise an exception.

calculate_possible_moves

I don’t understand this method. You spend a fair amount of code computing two variables that are local to the method. Then you return, without storing, processing, or returning those variables.

The code is not dead — there are two references to this method. But I think it isn’t doing anything.

calculate_possible_move_for_check

This function is described as «calculate». But your return values are boolean. This is not a calculation at all.

You use the color of the checker to determine a direction of movement. This means that the code applies to «men» but not to queens. That in turn suggests to me that you probably want to make the individual pieces members of a class, instead of just character strings.

Finally, the board is fixed in size. You should pre-compute the results of this function and store them in static data. That reduces the function to a lookup.

attack

You can use unpacking to assign multiple variables:

u_x, u_y = *usr_inp

There are three possibilities for a square. You check two of them to make sure the third is true:

if self.deck[u_x][u_y] != ' ' and self.deck[u_x][u_y] != self.color:

Just test for what you want:

if self.deck[u_x][u_y] == self.color:

Checking for ‘ ‘ and for self.color at a target address suggests that your class needs more methods:

if self.deck[u_x - 1][u_y + 1] == ' ':  # Up right

Could become:

target = self.up_right_of(usr_inp)
if self.is_empty(target):

With function self.is_enemy_of(color, location) available as well.

If you write methods for the various directions, you can iterate over the methods, which should shorten this code a lot. Instead of separate sections for x+1, y-1 and x-1,y-1 and …, just make a tuple and iterate over it:

for direction in self.up_right_of, self.up_left_of, self.down_right_of, self.down_left_of:
    target = direction(usr_inp)

move

You behave in different ways based on some attribute of the data. That is a key indicator that you need a class. I suggest making two classes: «men» and «queens», and defining the __str__ method to return the current string values.

This code is too complex:

if self.color == '●' and cur_check in self.queen_list_w or self.color != '●' and cur_check in self.queen_list_b:

You check for (color is black) or (color is not black). That’s always going to be true. Just delete the color discrimination, and check for membership in the queen lists.

But really, just make classes for the pieces and delete all this code.

bot.py

class Bot

There’s a lot of redundant data stored in this class. You’ve got the deck, the checkers, enemy checkers, queens. All of which is also in the deck.

I suggest that you look hard at how you are using the data, and implement methods in the Deck class to provide that data instead.
This would mean data being in just one place, eliminating a source of error.

I’m going to skip further review of this, since I think the suggestion to create objects for the pieces and move the data back into the Deck class will change this class pretty much everywhere.

Welcome to CodeReview! I’ll start by saying thank you for posting a somewhat-complex package of code. At first blush, your code appears to be written in the generally-accepted style and appears well organized and somewhat documented.

Functionality

Before I actually review the code, I’ll point out that I tried to play the game. Here’s my experience:

Your color is: ○
     1 2 3 4 5 6 7 8

A   | |●| |●| |●| |●|   A
B   |●| |●| |●| |●| |   B
C   | |●| |●| |●| |●|   C
D   | | | | | | | | |   D
E   | | | | | | | | |   E
F   |○| |○| |○| |○| |   F
G   | |○| |○| |○| |○|   G
H   |○| |○| |○| |○| |   H

     1 2 3 4 5 6 7 8
Choose you checkerf1
Enter coordinatese1
Choose you checkerf1
Enter coordinatese2
Test Error
Your color is: ●
     1 2 3 4 5 6 7 8

A   | |●| |●| |●| |●|   A
B   |●| |●| |●| |●| |   B
C   | |●| |●| |●| |●|   C
D   | | | | | | | | |   D
E   | |○| | | | | | |   E
F   | | |○| |○| |○| |   F
G   | |○| |○| |○| |○|   G
H   |○| |○| |○| |○| |   H

     1 2 3 4 5 6 7 8

So I have some comments on the actual operation of the game :-).

Playability

First, the game board does not color black/white squares. This makes it harder to play, since the alternating colors help with distinguishing different rows/columns, as well as providing a visual crutch for understanding move possibilities.

Next, the coordinate system is perfectly functional, but not at all intuitive. I’d suggest you consider some alternatives, like labelling the individual checkers with letters A-L instead of circles. Similarly, you might consider enumerating the possible destinations for movement. Either explicitly (redraw the board with 1..4 markers) or implicitly (draw a compass rose with 1..4 on it alongside the board).

Input/Output

The prompt needs to include a space at the end. Otherwise, you get what I got:

Choose you checkerf1

There is no indication of a help system. If someone doesn’t know how to play checkers, or doesn’t remember, how do they get help? Where are the rules of play?

If I enter a bad move, as I did, there’s no rebuff message. Instead, just a new prompt. You should explain that my move is invalid, and either print a rule summary («You must move 1 space diagonally, or jump over an enemy piece diagonally») or print an enumeration of valid moves for the piece.

I don’t know what a Test Error is. But I’m pretty sure that it shouldn’t be appearing during gameplay.

Naming

I have two comments on your file naming. First, instead of game_loop.py you should have named the file checkers.py or main.py (or __main__.py). Because there’s nothing obvious about what game this is, just looking at the file names. I mean, deck_and_cheez.py? What game did you start writing before you switched to checkers?

Second, there just isn’t that much code in these files:

aghast@laptop:~/Code/so/checkers$ wc -l *py
  312 bot.py
  692 deck_and_cheez.py
   58 game_loop.py
 1062 total

Why not just move all the code into checkers.py? This isn’t java, there’s no requirement to have a bunch of little files laying around.

game_loop.py

Structure

Everything in this file should be in a function. Possibly a different function in a different class, but definitely in a function. The standard idiom for python scripts is this:

def main():
    ... all the stuff that isn't in a function ...

if __name__ == '__main__':
    main()

Use this idiom. It makes it possible to do a bunch of things, including writing unit tests, that will benefit you. It doesn’t cost much (just one line, and some tabs), and pays off quickly.

Modularity

What do I learn from this code? (I deleted some vertical space for convenience.)

colors = ['○', '●']
deck = deck_and_cheez.Deck(random.choice(colors))
checker_pos = deck_and_cheez.CurrentChecker()
ALLIES = None
ENEMY = None

while True:
    print(f'Your color is: {deck.color}')
    deck.print_current_deck()

    if ALLIES is None:
        ALLIES = deck.color
    elif ENEMY is None:

Three things. First, is that your deck_and_cheez.Deck class is broken. Second, your deck_and_cheez.CurrentChecker class is even worse! Third, you aren’t taking a broad enough view.

class Deck

A class is supposed to be self-sufficient. If you give it the required arguments at creation time, the returned instance will stand alone.

Let’s look:

  • Naming: checkers is a «board game». A better name for Deck would be GameState or Board. In English, a deck is either a floor on a ship, or an alias for a pack of cards. Poker and Pinochle are played with a Deck, while Checkers and Chess are played with a Board.

  • From the colors = [...] variable, you don’t have a Deck.COLORS that provides this data to callers. Yet, this is a pretty important part of the Deck so why isn’t it there?

  • From the Deck(random.choice(colors)), it seems you don’t need to tell Deck what both colors are (you only pass in one color). Thus, I sense there is a second copy of the colors = [...] over in the Deck class somewhere. (In fact, it’s worse. See below.)

  • The code to set ALLIES and ENEMY is determining the value of the random choice you passed in as a parameter. And the two «constants» are only used to determine whose turn it is to play. This could be a part of Deck. It also could be implemented in code, just by writing player_turn() ; computer_turn().

Suggestions

I don’t think you need to «specify» a player color on creation of a new Deck. I think you should just randomly pick one, and make it available to users of the class:

deck = Deck()
player_color, bot_color = deck.player_colors

Once you have the colors allocated inside Deck, you can write a method that cycles the player-turn tracking without having to pass in any parameters:

deck.end_turn()

You should provide a mechanism for determining the end of the game. That could be a method on Deck or an exception raised by the turn handlers. Doing this makes the game loop clearer.

while deck.in_progress:

or

try:
    while True:
        player_turn()
        robot_turn()
except GameOver:
    pass

class CurrentChecker

This class is so low-key that I almost didn’t catch it. Your usage model is that you create an instance of the class:

checker_pos = deck_and_cheez.CurrentChecker()

and then later, during the human-player handling, you update it:

checker = input('Choose you checker').upper()

if deck.check_position(checker):
    if deck.check_checker_pos(checker):
        current_checker = checker_pos.coord(checker)

Problematically, you are calling methods on the deck class before you update the current_checker instance.

Suggestions

This class doesn’t do anything. Either delete it and just put all the functionality in the Deck class, or make it an internal class of the Deck.

Since you have most of the functionality implemented in Deck already, I suggest just deleting this class and letting the deck handle everything.

Narrow View

In your main loop, you are doing a bunch of things:

  • Tracking the current-player
  • Invoking the player or bot turn code
  • Implementing the move input mechanics
  • Looping to validate input
  • Updating the state of the Deck at each turn

To me, this says you need to work on the Deck class (see above), and also create another class or two. You need some code to handle player input mechanics and input validation logic. You need code to handle the simple gameplay mechanics.

I’d suggest creating a Player class, similar to the Bot class. Then you could just invoke the «play_turn()» method on two different objects.

The current-player problem can be solved by just calling players in sequence, as shown above.

The move mechanics and input validation are both part of the player interface. You could actually write different classes with different mechanics, and try them. Or make them play options, if you find that different people like different mechanics.

There should be no reason to update the deck state at the end of the turn. The deck should know enough about game play to update itself (it’s just to track whose turn it is…).

deck_and_cheez.py

First, what’s with the name? Why and_cheez?

class Deck

You have methods in this class that begin with __. Don’t do this.

print_current_deck

Your numbers list should just be a string.

Your loop could use enumerate to eliminate the letter_count variable.

I’d suggest just hard-coding 10 print statements. It would be about the same length and would make the output more clear:

print(f"t 1 2 3 4 5 6 7 8n")

print(f"At{'|'.join(self.deck[0])}tA")
print(f"Bt{'|'.join(self.deck[1])}tB")
...

But really, it isn’t the job of this class to communicate with the user. So instead of printing anything, just join the strings with a newline and return the resulting string:

return "n".join(f"t 1 2 3 4 5 6 7 8n",
        f"At{'|'.join(self.deck[0])}tA",
        ...)

__coordinates

Delete the ‘__’.

Rename this to express what it does: parse_user_coordinates

Use str.index to parse the input. It produces a more compact function:

row = "ABCDEFGH".index(usr_inp[0].upper())
col = "12345678".index(usr_inp[1])

return (row, col)

This will raise an exception if either input character is not matched. I think that’s a good way to return control to the code that’s driving the user interface, but you may want to catch the exception and do something different.

check_position

This is redundant with the method above. You print error messages, but it isn’t the job of this class to communicate with the user. Better, IMO, to return a value or raise an exception.

calculate_possible_moves

I don’t understand this method. You spend a fair amount of code computing two variables that are local to the method. Then you return, without storing, processing, or returning those variables.

The code is not dead — there are two references to this method. But I think it isn’t doing anything.

calculate_possible_move_for_check

This function is described as «calculate». But your return values are boolean. This is not a calculation at all.

You use the color of the checker to determine a direction of movement. This means that the code applies to «men» but not to queens. That in turn suggests to me that you probably want to make the individual pieces members of a class, instead of just character strings.

Finally, the board is fixed in size. You should pre-compute the results of this function and store them in static data. That reduces the function to a lookup.

attack

You can use unpacking to assign multiple variables:

u_x, u_y = *usr_inp

There are three possibilities for a square. You check two of them to make sure the third is true:

if self.deck[u_x][u_y] != ' ' and self.deck[u_x][u_y] != self.color:

Just test for what you want:

if self.deck[u_x][u_y] == self.color:

Checking for ‘ ‘ and for self.color at a target address suggests that your class needs more methods:

if self.deck[u_x - 1][u_y + 1] == ' ':  # Up right

Could become:

target = self.up_right_of(usr_inp)
if self.is_empty(target):

With function self.is_enemy_of(color, location) available as well.

If you write methods for the various directions, you can iterate over the methods, which should shorten this code a lot. Instead of separate sections for x+1, y-1 and x-1,y-1 and …, just make a tuple and iterate over it:

for direction in self.up_right_of, self.up_left_of, self.down_right_of, self.down_left_of:
    target = direction(usr_inp)

move

You behave in different ways based on some attribute of the data. That is a key indicator that you need a class. I suggest making two classes: «men» and «queens», and defining the __str__ method to return the current string values.

This code is too complex:

if self.color == '●' and cur_check in self.queen_list_w or self.color != '●' and cur_check in self.queen_list_b:

You check for (color is black) or (color is not black). That’s always going to be true. Just delete the color discrimination, and check for membership in the queen lists.

But really, just make classes for the pieces and delete all this code.

bot.py

class Bot

There’s a lot of redundant data stored in this class. You’ve got the deck, the checkers, enemy checkers, queens. All of which is also in the deck.

I suggest that you look hard at how you are using the data, and implement methods in the Deck class to provide that data instead.
This would mean data being in just one place, eliminating a source of error.

I’m going to skip further review of this, since I think the suggestion to create objects for the pieces and move the data back into the Deck class will change this class pretty much everywhere.

Не так давно решил создать шашки на python. В какой-то момент возникла необходимость просчитать все возможные поля взятий для какой — либо конкретной шашки. Для этой задачи я решил использовать рекурсию:
выясняем, есть ли возможность побить в четырех направлениях, если такая возможность есть, то добавляем эту позицию в массив, после чего рекурсивно вызываем эту же функцию для новой позиции, ведь, как известно, шашки могут бить сразу несколько других. Все позиции я просчитывал с помощью массива 8 х 8:

# создаем массив 8 * 8. Каждый элемент "-1"
# будет обозначать вражескую шашку, а элемент
# 1 - наши шашки.
virtual_board = [ [0] * 8 for x in range(8) ]

# поставим нашу шашку в левый верхний угол и попробуем
# вычислить позиции для битья двух других, вражеских шашек
virtual_board[0][0] = 1
virtual_board[1][1] = -1
virtual_board[1][3] = -1 

class Example():
    def __init__(self, virtual_board):
        self.virtual_board = virtual_board
        self.positions = list()

    def check_taking_steps_for_player(self, x, y):
        # существует ли данная позиция?
        if (x >= 0 and x < 8) and (y >= 0 and y < 8):   
            # если шашка находится слишком близко к 
            # низу или правому краю, то взятие в данном направлении невозможно
            if y < 6 and x < 6:
                # проверка нижнего правого угла

                # сначала выясняем, находится ли в нужном направлении
                # вражеская шашка. Затем проверяем клетку 'за ней'.
                # Если такая клетка свободна, то взятие возможно.
                if self.virtual_board[y + 1][x + 1] < 0 and 
                self.virtual_board[y + 2][x + 2] == 0:
                    self.positions.append((x+2, y+2))  
                    self.check_taking_steps_for_player(x+2, y+2)

            if y >= 2 and x < 6:
                # верхнего правого угла
                if self.virtual_board[y - 1][x + 1] < 0 and 
                self.virtual_board[y - 2][x + 2] == 0:
                    self.positions.append((x+2, y-2))
                    self.check_taking_steps_for_player(x+2, y-2)


            if y >= 2 and x >= 2:
                # верхнего левого угла
                if self.virtual_board[y - 1][x - 1] < 0 and 
                self.virtual_board[y - 2][x - 2] == 0:
                    self.positions.append((x-2, y-2))
                    self.check_taking_steps_for_player(x-2, y-2)

            if y < 6 and x >= 2:
                # нижнего левого угла
                if self.virtual_board[y + 1][x - 1] < 0 and 
                self.virtual_board[y + 2][x - 2] == 0:
                    self.positions.append((x-2, y+2))
                    self.check_taking_steps_for_player(x-2, y+2)

ex = Example(virtual_board)
#ex.check_taking_steps_for_player(0, 0)

Моя проблема в том, что программа просто-напросто зависает (она не справляется даже с этим примером, где вычислить нужно всего две позиции.) И это странно, ведь такая рекурсивная функция не предполагает слишком сильного углубления. В чем беда?

A Python3 library that you can use to play a game of checkers/draughts. This is just a set of classes that you can use in your code, it’s not an interactive shell checkersgame.

  • Version: 1.4.2

Build Status

Assumptions

The rules used are for competitive American checkers or English draughts. This means an 8×8 board with force captures and regular kings.

Each position on the board is numbered 1 to 32. Each move is represented as an array with two values: starting position and ending position. So if you’re starting a new game, one of the available moves is [9, 13] for player 1. If there’s a capture move, the ending position is the position the capturing piece will land on (i.e. two rows from its original row), which might look like [13, 22].

Each piece movement is completely distinct, even if the move is part of a multiple capture series. In Portable Draughts Notation mutli-capture series are usually represented by a 5-32 (for a particularly long series of jumps), but in certain situations there could be multiple pathways to achieve that final position. This game requires an explicit spelling out of each distinct move in the multi-capture series.

Usage

Create a new game:

from checkers.game import Game

game = Game()

See whose turn it is:

game.whose_turn() #1 or 2

Get the possible moves:

game.get_possible_moves() #[[9, 13], [9, 14], [10, 14], [10, 15], [11, 15], [11, 16], [12, 16]]

Make a move:

game.move([9, 13])

Check if the game is over:

game.is_over() #True or False

Find out who won:

game.get_winner() #None or 1 or 2

Review the move history:

game.moves #[[int, int], [int, int], ...]

Change the consecutive noncapture move limit (default 40 according to the rules):

game.consecutive_noncapture_move_limit = 20
game.move_limit_reached() #True or False

Review the pieces on the board:

for piece in game.board.pieces:
	piece.player #1 or 2
	piece.other_player #1 or 2
	piece.king #True or False
	piece.captured #True or False
	piece.position #1-32
	piece.get_possible_capture_moves() #[[int, int], [int, int], ...]
	piece.get_possible_positional_moves() #[[int, int], [int, int], ...]

Testing

Run python3 -m unittest discover from the root.

Permalink

Cannot retrieve contributors at this time

Checkers AI

A checkers ai that plays against a human. Includes GUI for the game.

Installation

  1. Download the repo

  2. Run pip install -r requirements.txt

  3. Run python script.py

  4. Play

Algorithm

The AI uses minimax and alpha beta pruning

Permalink

Cannot retrieve contributors at this time

Checkers AI

A checkers ai that plays against a human. Includes GUI for the game.

Installation

  1. Download the repo

  2. Run pip install -r requirements.txt

  3. Run python script.py

  4. Play

Algorithm

The AI uses minimax and alpha beta pruning

Понравилась статья? Поделить с друзьями:
  • Как написать шахматы на питоне
  • Как написать шахматы на джава
  • Как написать шахматы на python
  • Как написать шахматы на java
  • Как написать шахматный движок