Skip to main content
Code Review

Return to Question

Tweeted twitter.com/StackCodeReview/status/1171982053371461636
edited tags; edited tags
Source Link
200_success
  • 145.5k
  • 22
  • 190
  • 478

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.

Source Link
QU1RK
  • 91
  • 4

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:

enter image description here

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
lang-py

AltStyle によって変換されたページ (->オリジナル) /