Really appreciate any of your suggestions for improvement of my code! It's very important me.
Really appreciate any of your suggestions for improvement of my code! It's very important me.
Simple Python3 Checkers
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?
Really appreciate any of your suggestions for improvement of my code! It's very important me.
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