I'm a beginning "pythonist" my current task is to write a checkers game and I need some suggestions here and there:
- are the code style and literacy answering the criteria of python?
- are the modules of my program correlating with each other correctly?
- are the algorithms of finding possibilities (actions) for chechers working right? (just to add on to that one, did I properly set up the interaction of algorithms between themselves?)
- is the bot that I've made adjustable to that program?
- how can I make bot's algorithm more advanced?
- I don't really like the way I made game loop, so can you suggest any pattern that I could use?
Also, less important question, how can I improve the performance of the code?
Here is how it looks:
game_loop.py
import random
import deck_and_cheez
import bot
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:
ENEMY = deck.color
bott = bot.Bot(deck.deck, ENEMY)
if deck.color == ALLIES:
while True:
checker = input('Choose you checker').upper()
if deck.check_position(checker):
if deck.check_checker_pos(checker):
current_checker = checker_pos.coord(checker)
move_coordinates = input('Enter coordinates').upper()
if deck.move(move_coordinates, current_checker):
deck.change_color()
break
elif not deck.move(move_coordinates, current_checker):
continue
elif deck.color == ENEMY:
bott.move_bot()
deck.deck = bott.deck
deck.change_color()
continue
deck_and_cheez.py:
class Deck:
def __init__(self, color):
"""
Function construct deck and store current information about checkers
:param color: color of your checkers
:rtype: str
"""
self.deck = [[' ', '●', ' ', '●', ' ', '●', ' ', '●'],
['●', ' ', '●', ' ', '●', ' ', '●', ' '],
[' ', '●', ' ', '●', ' ', '●', ' ', '●'],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
['○', ' ', '○', ' ', '○', ' ', '○', ' '],
[' ', '○', ' ', '○', ' ', '○', ' ', '○'],
['○', ' ', '○', ' ', '○', ' ', '○', ' ']]
self.color = color
self.w_checker = []
self.b_checker = []
self.queen_list_w = []
self.queen_list_b = []
def print_current_deck(self):
"""
Function prints current deck
"""
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
numbers = ['1', '2', '3', '4', '5', '6', '7', '8']
letter_count = 0
print(f'\t {" ".join(numbers)}\n')
for line in self.deck:
a = '|'.join(line) # Разделить вертикальніми каждую яцеую и букві и ціфри добавить Можно джоинть по символу
print(f'{letters[letter_count]}\t|{a}|\t{letters[letter_count]}')
letter_count += 1
print(f'\n\t {" ".join(numbers)}')
def __coordinates(self, usr_inp):
"""
Function user friendly input to computer
:param usr_inp: Coordinate of cell
:return: x and y coordinate
:rtype: tuple
"""
dict_pos = {'A': 0,
'B': 1,
'C': 2,
'D': 3,
'E': 4,
'F': 5,
'G': 6,
'H': 7
}
if usr_inp[0] not in dict_pos.keys():
return False
x = dict_pos[usr_inp[0]]
y = int(usr_inp[1])
return x, y - 1
def check_position(self, usr_inp):
"""
Function checks whether coordinates are correct or not
:param usr_inp: Coordinates
:return: Coordinates
:rtype: tuple
"""
coordinates = self.__coordinates(usr_inp)
if not self.__coordinates(usr_inp):
print('Invalid letter')
return False
elif coordinates[0] < 0 or coordinates[1] < 0:
print('Your coordinates is negative1')
print('Please enter correct coordinate1')
return False
elif coordinates[0] > 8 or coordinates[1] > 8:
print('Your coordinates out from scope2')
print('Please enter correct coordinate2')
return False
return coordinates
def calculate_possible_moves(self):
"""
Function calculates white and black checkers and possible move for them
:return:
"""
self.w_checker.clear()
self.b_checker.clear()
for l_index, line in enumerate(self.deck):
for e_index, element in enumerate(line):
if element == '●':
self.w_checker.append((l_index, e_index))
elif element == '○':
self.b_checker.append((l_index, e_index))
move_checker_w = []
move_checker_b = []
move_checker_w.clear()
move_checker_b.clear()
for coordinate in self.w_checker:
try:
left_cor = self.deck[coordinate[0 ] +1][coordinate[1 ] +1]
right_cor = self.deck[coordinate[0 ] +1][coordinate[1 ] -1]
if left_cor == ' ':
move_checker_w.append((coordinate[0 ] +1, coordinate[1 ] +1))
if right_cor == ' ':
move_checker_w.append((coordinate[0 ] +1, coordinate[1 ] -1))
except IndexError:
pass
for coordinate in self.b_checker:
try:
left_cor = self.deck[coordinate[0] - 1][coordinate[1] - 1]
right_cor = self.deck[coordinate[0] - 1][coordinate[1] + 1]
if left_cor == ' ':
move_checker_b.append((coordinate[0 ] -1, coordinate[1 ] -1))
if right_cor == ' ':
move_checker_b.append((coordinate[0 ] -1, coordinate[1 ] +1))
except IndexError:
pass
def calculate_possible_move_for_check(self, usr_inp, cur_check):
"""
Function calculates possible move for each checker
:param usr_inp: Coordinate of move
:param cur_check: Coordinate of checker
:return: True or False
:rtype: bool
"""
if self.color == '●':
if self.deck[cur_check[0]][cur_check[1]] == self.color:
if usr_inp[0] == cur_check[0] + 1:
if usr_inp[0] > cur_check[0] and sum(usr_inp) == sum(cur_check): # Left variant
if self.check_cell(usr_inp):
return False
return True
elif usr_inp[0] > cur_check[0] and sum(usr_inp) == sum(cur_check) + 2: # Right variant
if self.check_cell(usr_inp):
return False
return True
else:
return False
elif self.color == '○':
if usr_inp[0] == cur_check[0] - 1:
if usr_inp[0] < cur_check[0] and sum(usr_inp) == sum(cur_check) - 2: # Left variant
if self.check_cell(usr_inp):
return False
return True
elif usr_inp[0] < cur_check[0] and sum(usr_inp) == sum(cur_check): # Right variant
if self.check_cell(usr_inp):
return False
return True
else:
return False
def attack(self, usr_inp, cur_check):
"""
Function describe attack
:param usr_inp: Coordinate of move
:param cur_check: Coordinate of checker
:return: True or False
:rtype: bool
"""
u_x = usr_inp[0]
u_y = usr_inp[1]
c_x = cur_check[0]
c_y = cur_check[1]
try:
if self.deck[u_x][u_y] != ' ' and self.deck[u_x][u_y] != self.color:
if self.deck[u_x - 1][u_y + 1] == ' ': # Up right
if self.deck[c_x - 1][c_y + 1] != ' ' and self.deck[c_x - 1][c_y + 1] != self.color:
if u_x == c_x - 1 and u_y == c_y + 1:
self.deck[c_x][c_y] = ' '
self.deck[u_x][u_y] = ' '
self.deck[u_x - 1][u_y + 1] = self.color
return True
except IndexError:
pass
try:
if self.deck[u_x - 1][u_y - 1] == ' ': # Up left
if self.deck[c_x - 1][c_y - 1] != ' ' and self.deck[c_x - 1][c_y - 1] != self.color:
if u_x == c_x - 1 and u_y == c_y - 1:
self.deck[c_x][c_y] = ' '
self.deck[u_x][u_y] = ' '
self.deck[u_x - 1][u_y - 1] = self.color
return True
except IndexError:
pass
try:
if self.deck[u_x + 1][u_y - 1] == ' ': # Down left
if self.deck[c_x + 1][c_y - 1] != ' ' and self.deck[c_x + 1][c_y - 1] != self.color:
if u_x == c_x + 1 and u_y == c_y - 1:
self.deck[c_x][c_y] = ' '
self.deck[u_x][u_y] = ' '
self.deck[u_x + 1][u_y - 1] = self.color
return True
except IndexError:
pass
try:
if self.deck[u_x + 1][u_y + 1] == ' ': # Down right
if self.deck[c_x + 1][c_y + 1] != ' ' and self.deck[c_x + 1][c_y + 1] != self.color:
if u_x == c_x + 1 and u_y == c_y + 1:
self.deck[c_x][c_y] = ' '
self.deck[u_x][u_y] = ' '
self.deck[u_x + 1][u_y + 1] = self.color
return True
except IndexError:
pass
else:
print('Test Error')
return False
def move(self, usr_inp, cur_check):
"""
Function describe move and this function main in module
all magic starst from here
:param usr_inp: String user friendly coordinate
:param cur_check: Coordinate
:return: True or False
:rtype: bool
"""
move_coordinates = tuple(self.check_position(usr_inp))
if self.is_queen(cur_check):
if self.color == '●' and cur_check in self.queen_list_w or self.color != '●' and cur_check in self.queen_list_b:
print('You choose a Queen')
if self.play_like_a_queen(move_coordinates, cur_check):
return True
return False
self.calculate_possible_moves()
if self.calculate_possible_move_for_check(move_coordinates, cur_check):
if len(self.attack_list()) == 0:
if not self.attack(move_coordinates, cur_check):
self.deck[move_coordinates[0]][move_coordinates[1]] = self.color
self.deck[cur_check[0]][cur_check[1]] = ' '
return True
elif len(self.attack_list()) > 0 and not self.attack(move_coordinates, cur_check):
print('You must attack')
return False
while len(self.attack_list()) > 0:
self.print_current_deck()
print(self.color)
self.move(input('Next attack').upper(), self.__calculate_new_cords(move_coordinates, cur_check))
return True
elif not self.calculate_possible_move_for_check(move_coordinates, cur_check):
if len(self.attack_list()) > 0:
if self.attack(move_coordinates, cur_check):
return True
print('You must attack')
return False
while len(self.attack_list()) > 0:
self.print_current_deck()
print(self.color)
new_move = input('Next attack').upper()
self.move(new_move, self.__calculate_new_cords(move_coordinates, cur_check))
return True
return False
def check_checker_pos(self, usr_inp):
"""
Function check if checker in cell
:param usr_inp: Move coordinate
:return: coordinate
:rtype:tuple
"""
coordinates = self.__coordinates(usr_inp)
if self.deck[coordinates[0]][coordinates[1]] == '':
print('Is no checker here')
print('Please enter correct coordinate')
return False
return coordinates
def change_color(self):
"""
Function change color
:return:
"""
if self.color == '●':
self.color = '○'
else:
self.color = '●'
def check_cell(self, usr_inp):
"""
Function check if cell is empty
:param usr_inp: Move coordinate
:return: True or False
:rtype: bool
"""
if self.deck[usr_inp[0]][usr_inp[1]] != ' ' and len(self.attack_list()) == 0:
print('This cell is field')
return True
return False
def can_attack(self, cur_check):
"""
Function check if checker can attack
:param cur_check: Checker coordinate
:return: True or False
:rtype: bool
"""
x = cur_check[0]
y = cur_check[1]
try:
if self.deck[x - 1][y - 1] != ' ' and self.deck[x - 1][y - 1] != self.color and self.deck[x - 2][y - 2] == ' '\
and self.deck[x][y] == self.color and x - 1 >= 0 and y - 1 >= 0: # Left up
return True
except IndexError:
pass
try:
if self.deck[x - 1][y + 1] != ' ' and self.deck[x - 1][y + 1] != self.color and self.deck[x - 2][y + 2] == ' '\
and self.deck[x][y] == self.color and x - 1 >= 0 and y + 1 < 8: # Right up
return True
except IndexError:
pass
try:
if self.deck[x + 1][y - 1] != ' ' and self.deck[x + 1][y - 1] != self.color and self.deck[x + 2][y - 2] == ' '\
and self.deck[x][y] == self.color and x + 1 < 8 and y >= 0: # Down left
return True
except IndexError:
pass
try:
if self.deck[x + 1][y + 1] != ' ' and self.deck[x + 1][y + 1] != self.color and self.deck[x + 2][y + 2] == ' '\
and self.deck[x][y] == self.color and x + 1 < 8 and y + 1 < 8: #Down right
return True
except IndexError:
pass
return False
def attack_list(self):
"""
Function generate attack list
:return: attack list
:rtype: list
"""
self.calculate_possible_moves()
if self.color == '●':
attack_list = []
for coordinate in self.w_checker:
if self.can_attack(coordinate):
attack_list.append(coordinate)
elif self.color == '○':
attack_list = []
for coordinate in self.b_checker:
if self.can_attack(coordinate):
attack_list.append(coordinate)
return attack_list
def __calculate_new_cords(self, usr_inp, cur_check):
"""
Calculate new coordinate for current checker
:param usr_inp: Move coordinate
:param cur_check: Checker coordinate
:return: x and y coordinate
:rtype: tuple
"""
u_x = usr_inp[0]
u_y = usr_inp[1]
c_x = cur_check[0]
c_y = cur_check[1]
a = -((u_x - c_x) * 2)
b = -((u_y - c_y) * 2)
x = c_x - a
y = c_y - b
return x, y
def is_queen(self, cur_check):
"""
Function check if checker is a queen
:param cur_check: Current checker
:return: True or false
:rtype: bool
"""
if self.color == '●' and cur_check[0] == 7:
self.queen_list_w.append(cur_check)
return True
elif self.color == '○' and cur_check[0] == 0:
self.queen_list_b.append(cur_check)
return True
return False
def play_like_a_queen(self, usr_inp, cur_check):
"""
Function describe queen logic
:param usr_inp: Move coordinate
:param cur_check: Checker coordinate
:return: True or False
:rtype: bool
"""
u_x = usr_inp[0]
u_y = usr_inp[1]
c_x = cur_check[0]
c_y = cur_check[1]
if u_x != c_x or u_y != c_y:
if self.deck[u_x][u_y] == self.color:
print('You cant move self checker')
elif self.deck[u_x][u_y] != self.color and self.deck[u_x][u_y] != ' ':
try:
if self.deck[u_x - 1][u_y - 1] == ' ': # Up Left
self.deck[u_x - 1][u_y - 1] = self.color
self.deck[u_x][u_y] = ' '
return True
except IndexError:
pass
try:
if self.deck[u_x - 1][u_y + 1] == ' ': #Up Right
self.deck[u_x - 1][u_y + 1] = self.color
self.deck[u_x][u_y] = ' '
return True
except IndexError:
pass
try:
if self.deck[u_x + 1][u_y - 1] == ' ': #Down left
self.deck[u_x + 1][u_y - 1] = self.color
self.deck[u_x][u_y] = ' '
return True
except IndexError:
pass
try:
if self.deck[u_x + 1][u_y + 1] == ' ': # Down left
self.deck[u_x + 1][u_y + 1] = self.color
self.deck[u_x][u_y] = ' '
return True
except IndexError:
pass
return False
else:
self.deck[u_x][u_y] = '●'
self.deck[c_x][c_y] = ' '
return True
def is_exit(self, usr_inp):
"""
Function describe exit from input
:param usr_inp: Some input
:return: True or False
:rtype: bool
"""
if usr_inp == 'R':
return True
class CurrentChecker:
"""
Descibe current checker
"""
def __init__(self, coordinates=None):
"""
Construct current checker
:param coordinates:
"""
self.coordinates = coordinates
def coord(self, usr_inp):
"""
Function get correct coordinate
:param usr_inp: Coordinate of checker
:return: Coordinates
:rtype: tuple
"""
cords = Deck(1).check_position(usr_inp)
self.coordinates = cords
return self.coordinates
bot.py
class Bot:
"""
Describe Bot
"""
def __init__(self, deck, color):
"""
Function construct bot
:param deck: Current deck
:param color: Bot color
"""
self.deck = deck
self.color = color
self.checkers = []
self.enemy_checkers = []
self.moves = []
self.queen_list = []
def search_for_checker(self):
"""
Function search all possible action for bot
:return:
"""
for l_index, line in enumerate(self.deck):
for e_index, element in enumerate(line):
if element == self.color:
self.checkers.append((l_index, e_index))
elif element != self.color and element != ' ':
self.enemy_checkers.append((l_index, e_index))
elif element == ' ':
try:
if self.color == '●':
if self.deck[l_index - 1][e_index - 1] == self.color or self.deck[l_index - 1][e_index + 1] == self.color:
self.moves.append((l_index, e_index))
except IndexError:
pass
try:
if self.color != '●':
if self.deck[l_index + 1][e_index + 1] == self.color or self.deck[l_index + 1][e_index - 1] == self.color:
self.moves.append((l_index, e_index))
except IndexError:
pass
def move_bot(self):
"""
Function describe bot move
:return: Deck
:rtype: list
"""
self.is_queen_bot()
if self.attack_like_queen():
self.clears()
return self.deck
elif self.move_like_queen():
self.clears()
return self.deck
elif self.can_attack():
self.clears()
return self.deck
elif self.can_move():
self.deck[self.can_move()[1][0]][self.can_move()[1][1]] = self.color
self.deck[self.can_move()[0][0]][self.can_move()[0][1]] = ' '
self.clears()
return self.deck
def can_move(self, checker=None):
"""
Function check if bot can move
:param checker: Checker coordinate
:return: checker and move
:rtype: tuple
"""
self.search_for_checker()
for checker in self.checkers:
for move in self.moves:
c_x = checker[0]
c_y = checker[1]
m_x = move[0]
m_y = move[1]
if self.color == '●':
if c_x + 1 == m_x and c_y - 1 == m_y and self.deck[m_x][m_y] == ' ' or c_x + 1 == m_x and c_y + 1 == m_y and self.deck[m_x][m_y] == ' ':
return checker, move
elif self.color != '●':
if c_x - 1 == m_x and c_y - 1 == m_y and self.deck[m_x][m_y] == ' ' or c_x - 1 == m_x and c_y + 1 == m_y and self.deck[m_x][m_y] == ' ':
return checker, move
def can_attack(self):
"""
Function check if bot can attack
:return: True or False
:rtype: bool
"""
self.search_for_checker()
for checker in self.checkers:
for enemy in self.enemy_checkers:
if self.can_attack_more(checker, enemy):
return True
return False
def is_queen_bot(self):
"""
Function check check if checker is queen
:return:
"""
for checkers in self.checkers:
if self.color == '●' and checkers[0] == 7:
self.queen_list.append(checkers)
elif self.color == '○' and checkers[0] == 0:
self.queen_list.append(checkers)
def move_like_queen(self):
"""
Function describe logic of queen moving
:return:
"""
for queen in self.queen_list:
for move in self.moves:
q_x = queen[0]
q_y = queen[1]
m_x = move[0]
m_y = move[1]
if q_x != m_x or q_y != m_y:
return queen, move
def attack_like_queen(self):
"""
Function describe logic of queen attack
:return:
"""
for queen in self.queen_list:
for enemy in self.enemy_checkers:
q_x = queen[0]
q_y = queen[1]
e_x = enemy[0]
e_y = enemy[1]
if q_x != e_x or q_y != e_y:
try:
if self.deck[((q_x - e_x)/2) - e_x][((q_y - e_y)/2) - e_y] == ' ':
new_pos = (((q_x - e_x)/2) - e_x, ((q_y - e_y)/2) - e_y)
self.deck[q_x][q_y] = ' '
self.deck[e_x][e_y] = ' '
self.deck[new_pos[0]][new_pos[1]] = self.color
return queen, enemy, new_pos
except IndexError:
pass
return False
return False
def clears(self):
"""
Clear unused list
:return:
"""
self.checkers.clear()
self.enemy_checkers.clear()
self.moves.clear()
def can_attack_more(self, checker, enemy):
"""
Check if bot can attack again
:param checker: Checker coordinate
:param enemy: Enemy coordinate
:return: True or False
:rtype: bool
"""
c_x = checker[0]
c_y = checker[1]
e_x = enemy[0]
e_y = enemy[1]
try:
if c_x - e_x == 1 and c_y - e_y == 1 and self.deck[e_x + 1][e_y + 1] == ' ': # Up Left
self.deck[c_x][c_y] = ' '
self.deck[e_x][e_y] = ' '
self.deck[e_x - 1][e_y - 1] = self.color
for enemy in self.enemy_checkers:
if self.can_attack_more((e_x - 1, e_y - 1), enemy):
return True
return True
except IndexError:
pass
try:
if c_x - e_x == 1 and c_y - e_y == -1 and self.deck[e_x - 1][e_y + 1] == ' ': # Up Right
self.deck[c_x][c_y] = ' '
self.deck[e_x][e_y] = ' '
self.deck[e_x - 1][e_y + 1] = self.color
for enemy in self.enemy_checkers:
if self.can_attack_more((e_x - 1, e_y + 1), enemy):
return True
return True
except IndexError:
pass
try:
if c_x - e_x == -1 and c_y - e_y == 1 and self.deck[e_x - 1][e_y + 1] == ' ': # Down left
self.deck[c_x][c_y] = ' '
self.deck[e_x][e_y] = ' '
self.deck[e_x + 1][e_y - 1] = self.color
for enemy in self.enemy_checkers:
if self.can_attack_more((e_x + 1, e_y - 1), enemy):
return True
return True
elif c_x - e_x == -1 and c_y - e_y == -1 and self.deck[e_x - 1][e_y - 1] == ' ': # Down right
self.deck[c_x][c_y] = ' '
self.deck[e_x][e_y] = ' '
self.deck[e_x + 1][e_y + 1] = self.color
for enemy in self.enemy_checkers:
if self.can_attack_more((e_x + 1, e_y + 1), enemy):
return True
return True
except IndexError:
pass