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 beGameState
orBoard
. 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 aDeck.COLORS
that provides this data to callers. Yet, this is a pretty important part of theDeck
so why isn’t it there? -
From the
Deck(random.choice(colors))
, it seems you don’t need to tellDeck
what both colors are (you only pass in one color). Thus, I sense there is a second copy of thecolors = [...]
over in theDeck
class somewhere. (In fact, it’s worse. See below.) -
The code to set
ALLIES
andENEMY
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 ofDeck
. It also could be implemented in code, just by writingplayer_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 beGameState
orBoard
. 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 aDeck.COLORS
that provides this data to callers. Yet, this is a pretty important part of theDeck
so why isn’t it there? -
From the
Deck(random.choice(colors))
, it seems you don’t need to tellDeck
what both colors are (you only pass in one color). Thus, I sense there is a second copy of thecolors = [...]
over in theDeck
class somewhere. (In fact, it’s worse. See below.) -
The code to set
ALLIES
andENEMY
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 ofDeck
. It also could be implemented in code, just by writingplayer_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
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
-
Download the repo
-
Run
pip install -r requirements.txt
-
Run
python script.py
-
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
-
Download the repo
-
Run
pip install -r requirements.txt
-
Run
python script.py
-
Play
Algorithm
The AI uses minimax and alpha beta pruning