我刚刚创建了扫雷游戏,它的工作非常好(对我来说)。如能就如何改进这一守则提出任何建议,我们将不胜感激:
游戏解释:
我的方法:
清洁版
import random
class Minesweeper:
def __init__(self,width=9,height=10,mine_numbers=12):
self.width = width
self.height = height
self.mine_numbers = mine_numbers
self.table = [None]*self.width*self.height
self.user_cell = False
self.user_row = False
self.user_column = False
self.user_reveal = []
def game_create(self):
print(f'Default size is {self.width}*{self.height}, {self.mine_numbers} mines')
default_size = input('Play default size?(Y/N): ')
if default_size.lower() == 'n':
correct_input = False
while not correct_input:
try:
self.width = int(input('Enter width: '))
self.height = int(input('Enter height: '))
self.mine_numbers = int(input('Enter number of mines: '))
if self.mine_numbers >= self.width*self.height or self.mine_numbers == 0:
print('ERROR: Number of mines can not be 0 or equal/exceed table size')
elif self.width > 99 or self.height > 99:
print('ERROR: Maximum table size is 99*99')
else:
self.table = [None]*self.width*self.height
self.user_reveal = []
correct_input = True
return self.width,self.height,self.mine_numbers,self.table,self.user_reveal
except ValueError:
print('ERROR: Try again, number only')
else:
self.table = [None]*self.width*self.height
self.user_reveal = []
return self.width,self.height,self.mine_numbers,self.table,self.user_reveal
def user_input(self):
correct_input = False
while not correct_input:
try:
self.user_cell = input('Enter {[column][row]} in 4 digits eg. 0105: ')
int(self.user_cell)
if len(self.user_cell) != 4:
print('ERROR: Only 4 digits allowed')
elif int(self.user_cell[2:]) > self.height or self.user_cell[2:] == '00':
print('ERROR: Row out of range')
elif int(self.user_cell[:2]) > self.width or self.user_cell[:2] == '00':
print('ERROR: Column of range')
elif self.user_cell in self.user_reveal:
print('ERROR: Already revealed')
else:
correct_input = True
except ValueError:
print('ERROR: Try again, number only')
if self.user_cell:
self.user_row = int(self.user_cell[2:])
self.user_column = int(self.user_cell[:2])
self.user_reveal.append(self.user_cell)
return self.user_cell,self.user_row,self.user_column
def mines_generator(self):
user_location = ((self.user_row-1)*self.width)+self.user_column-1
possible_location = [i for i in range(self.width*self.height) if i != user_location]
mines_location = random.sample(possible_location,self.mine_numbers)
for i in mines_location:
self.table[i] = 9
return self.table
def two_dimension_array(self):
for i in range(self.height):
self.table[i] = self.table[0+(self.width*i):self.width+(self.width*i)]
del self.table[self.height:]
return self.table
def complete_table(self):
temporary_table = [[None for _ in range(self.width)] for _ in range(self.height)]
for i in range(self.height):
for j in range(self.width):
if self.table[i][j] == 9:
temporary_table[i][j] = 9
continue
else:
counter = 0
for k in range(i-1,i+2):
if 0 <= k <= self.height-1:
for l in range(j-1,j+2):
if 0 <= l <= self.width-1:
if self.table[k][l] == 9:
counter += 1
continue
temporary_table[i][j] = counter
self.table = temporary_table
return self.table
def adjacent_zero(self,zero_cell):
if self.table[int(zero_cell[2:])-1][int(zero_cell[:2])-1] == 0:
for i in range(int(zero_cell[2:])-1-1,int(zero_cell[2:])-1+2):
if 0 <= i < self.height:
for j in range(int(zero_cell[:2])-1-1,int(zero_cell[:2])-1+2):
if 0 <= j < self.width:
if str(j+1).zfill(2)+str(i+1).zfill(2) not in self.user_reveal:
self.user_reveal.append(str(j+1).zfill(2)+str(i+1).zfill(2))
if self.table[i][j] == 0:
self.adjacent_zero(str(j+1).zfill(2)+str(i+1).zfill(2))
return self.user_reveal
def first_turn(self):
self.user_input()
self.mines_generator()
self.two_dimension_array()
self.complete_table()
self.adjacent_zero(self.user_cell)
def print_table(self):
print('\n'*10)
for row in range(self.height+1):
cell = '|'
for column in range(self.width+1):
if row == 0:
cell += f'{column:2}|'
continue
elif column == 0:
cell += f'{row:2}|'
continue
elif str(column).zfill(2)+str(row).zfill(2) in self.user_reveal:
cell += f'{self.table[row-1][column-1]:2}|'
continue
else:
cell += '{:>3}'.format('|')
print(cell)
def end_game(self):
def reveal_mine():
for i,j in enumerate(self.table):
for k,l in enumerate(j):
if l == 9:
self.table[i][k] = ‘XX’
if str(k+1).zfill(2)+str(i+1).zfill(2) not in self.user_reveal:
self.table[i][k] = ‘**’
self.user_reveal.append(str(k+1).zfill(2)+str(i+1).zfill(2))
if self.user_cell:
if self.table[self.user_row-1][self.user_column-1] == 9:
end_game = True
reveal_mine()
self.print_table()
print('YOU LOSE!')
elif len(self.user_reveal) == (self.width*self.height)-self.mine_numbers:
end_game = True
reveal_mine()
self.print_table()
print('YOU WIN!')
else:
end_game = False
else:
end_game = False
return end_game
def restart_game(self):
restart = input('Restart?(Y/N): ')
if restart.lower() == 'y':
return True
else:
return False
def main():
minesweeper = Minesweeper()
while True:
minesweeper.game_create()
minesweeper.print_table()
minesweeper.first_turn()
while not minesweeper.end_game():
minesweeper.print_table()
minesweeper.user_input()
minesweeper.adjacent_zero(minesweeper.user_cell)
if not minesweeper.restart_game():
break
if __name__ == '__main__':
main()评论版本
import random
class Minesweeper:
def __init__(self,width=9,height=10,mine_numbers=12):
# Table generate: change via tables_ize()
self.width = width
self.height = height
self.mine_numbers = mine_numbers
self.table = [None]*self.width*self.height
# User cell input
self.user_cell = False
self.user_row = False
self.user_column = False
self.user_reveal = []
'''
{user_reveal} is changed by
- {game_create()}: reset user_reveal
- {user_input()}: append input cell, cannot reveal all adjacent 0 here (first turn - table not yet generated)
- {adjacent_zero()}: reveal all adjacent 0
- {end_game()}: append all mines
'''
'''
{*_user_*},{user_cell} = {[column][row]}, index is 1 more than {*_cell_*} index
{*_cell_*} = {[row][column]}, index is 1 less than {*_user_*} index
'''
def game_create(self):
print(f'Default size is {self.width}*{self.height}, {self.mine_numbers} mines')
default_size = input('Play default size?(Y/N): ')
if default_size.lower() == 'n':
correct_input = False
while not correct_input:
try:
self.width = int(input('Enter width: '))
self.height = int(input('Enter height: '))
self.mine_numbers = int(input('Enter number of mines: '))
if self.mine_numbers >= self.width*self.height or self.mine_numbers == 0:
print('ERROR: Number of mines can not be 0 or equal/exceed table size')
elif self.width > 99 or self.height > 99:
print('ERROR: Maximum table size is 99*99')
else:
self.table = [None]*self.width*self.height
self.user_reveal = []
correct_input = True
return self.width,self.height,self.mine_numbers,self.table,self.user_reveal
except ValueError:
print('ERROR: Try again, number only')
else:
self.table = [None]*self.width*self.height
self.user_reveal = []
return self.width,self.height,self.mine_numbers,self.table,self.user_reveal
def user_input(self):
correct_input = False
while not correct_input:
try:
self.user_cell = input('Enter {[column][row]} in 4 digits eg. 0105: ')
int(self.user_cell)
if len(self.user_cell) != 4:
print('ERROR: Only 4 digits allowed')
elif int(self.user_cell[2:]) > self.height or self.user_cell[2:] == '00':
print('ERROR: Row out of range')
elif int(self.user_cell[:2]) > self.width or self.user_cell[:2] == '00':
print('ERROR: Column of range')
elif self.user_cell in self.user_reveal:
print('ERROR: Already revealed')
else:
correct_input = True
except ValueError:
print('ERROR: Try again, number only')
self.user_row = int(self.user_cell[2:])
self.user_column = int(self.user_cell[:2])
if self.user_cell:
self.user_reveal.append(self.user_cell)
return self.user_cell,self.user_row,self.user_column
def mines_generator(self):
# Exclude first cell from mines generator
user_location = ((self.user_row-1)*self.width)+self.user_column-1
possible_location = [i for i in range(self.width*self.height) if i != user_location]
mines_location = random.sample(possible_location,self.mine_numbers)
# Assign 'Location with mine' with 9
for i in mines_location:
self.table[i] = 9
return self.table
def two_dimension_array(self):
# Save table into 2D array
for i in range(self.height):
self.table[i] = self.table[0+(self.width*i):self.width+(self.width*i)]
# Remove unnessessary elements
del self.table[self.height:]
return self.table
def complete_table(self):
# Create temporary 2D array
temporary_table = [[None for _ in range(self.width)] for _ in range(self.height)]
# For every table[i][j]
for i in range(self.height):
for j in range(self.width):
# If table[i][j] is bomb, continue
if self.table[i][j] == 9:
temporary_table[i][j] = 9
continue
else:
counter = 0
# For every adjacent neighbor arrays
for k in range(i-1,i+2):
# Error handling: list index out of range
if 0 <= k <= self.height-1:
for l in range(j-1,j+2):
# Error handling: list index out of range
if 0 <= l <= self.width-1:
# Count every adjacent mines
if self.table[k][l] == 9:
counter += 1
continue
temporary_table[i][j] = counter
self.table = temporary_table
return self.table
def adjacent_zero(self,zero_cell):
# If value is 0
if self.table[int(zero_cell[2:])-1][int(zero_cell[:2])-1] == 0:
# For all neighbor elements
for i in range(int(zero_cell[2:])-1-1,int(zero_cell[2:])-1+2):
# Error handling: index out of range
if 0 <= i < self.height:
for j in range(int(zero_cell[:2])-1-1,int(zero_cell[:2])-1+2):
if 0 <= j < self.width:
# If neighbor element of 0 is not yet append, append all adjacent element
if str(j+1).zfill(2)+str(i+1).zfill(2) not in self.user_reveal:
self.user_reveal.append(str(j+1).zfill(2)+str(i+1).zfill(2))
# If neighbor is also 0, do a recursion
if self.table[i][j] == 0:
self.adjacent_zero(str(j+1).zfill(2)+str(i+1).zfill(2))
def first_turn(self):
self.user_input()
self.mines_generator()
self.two_dimension_array()
self.complete_table()
self.adjacent_zero()
def print_table(self):
# Clear UI
print('\n'*10)
for row in range(self.height+1):
cell = '|'
for column in range(self.width+1):
# Top-row label
if row == 0:
cell += f'{column:2}|' # (Note: try 02 instead of 2)
continue
# First column label
elif column == 0:
cell += f'{row:2}|'
continue
# Revealed cell
elif str(column).zfill(2)+str(row).zfill(2) in self.user_reveal:
cell += f'{self.table[row-1][column-1]:2}|'
continue
# Not yet revealed cell
else:
cell += '{:>3}'.format('|')
print(cell)
def end_game(self):
# If end: reveal all mines, nested function
def reveal_mine():
for i,j in enumerate(self.table):
for k,l in enumerate(j):
if l == 9:
self.table[i][k] = ‘XX’
if str(k+1).zfill(2)+str(i+1).zfill(2) not in self.user_reveal:
self.table[i][k] = ‘**’
self.user_reveal.append(str(k+1).zfill(2)+str(i+1).zfill(2))
# If user choose cell: check if end
if self.user_cell:
if self.table[self.user_row-1][self.user_column-1] == 9:
end_game = True
reveal_mine()
self.print_table()
print('YOU LOSE!')
elif len(self.user_reveal) == (self.width*self.height)-self.mine_numbers:
end_game = True
reveal_mine()
self.print_table()
print('YOU WIN!')
else:
end_game = False
# If no cell selected: end = False
else:
end_game = False
return end_game
def restart_game(self):
restart = input('Restart?(Y/N): ')
if restart.lower() == 'y':
return True
else:
return False
def main():
minesweeper = Minesweeper()
while True:
minesweeper.game_create()
minesweeper.print_table()
minesweeper.first_turn()
while not minesweeper.end_game():
minesweeper.print_table()
minesweeper.user_input()
minesweeper.adjacent_zero(minesweeper.user_cell)
if not minesweeper.restart_game():
break
if __name__ == '__main__':
main()发布于 2020-01-02 15:14:34
我认为实现一个游戏是学习一种新的编程语言的好方法。如果您已经准备好了,您可以尝试使用游戏库(如派克或拱廊 )编写Minesweeper的图形版本,以便更多地了解该语言。
以下是我对改进的建议,没有特别的顺序:
当我检查代码时,我会同时阅读代码和注释。这些注释应该能帮助我理解代码的作用。但是对于这段代码,我不认为它们是这样的,因为它们处于相当低的水平上。告诉我与代码本身本质上相同的注释并不是很有趣。相反,注释应该传达的是代码的意图。为什么不知道怎么做。
我建议不要将注释放在单独的行上,而是将它们全部删除,并在模块的顶部用一个大注释来替换它们,在这个模块中,您可以描述Minesweeper类的体系结构:
import random
# Minesweeper
# -----------
# Minesweeper is a solitaire game in which the goal is to reveal all
# mines on a board. The Minesweeper class implements the game. It has
# the following attributes:
#
# * width: width of board
# ...
# * table: board representation
#
# This example shows how the class should be used:
#
# ms = Minesweeper()
# while True:
# ms.game_create()
# ...它为读者提供了代码的概述。
在这个堆栈溢出的答案中表示的一种约定是将“是”/“否”提示符中的默认选择大写。所以如果提示符是
Play default size? [Y/n]:用户知道,只要按回车,就会选择默认大小。是的,我知道这是一个“小细节”,但要制作一个伟大的软件,你必须考虑这些小事情。
如果用户不想使用默认大小,则会提示用户输入地雷的宽度、高度和数量。考虑制作一个封装提示代码的函数:
def prompt_int(name, lo, hi):
while True:
s = input(f'Enter {name} [{lo}..{hi}]: ')
try:
v = int(s)
except ValueError:
print('ERROR: Try again, number only')
continue
if not (lo <= v <= hi):
print('ERROR: Allowed range %d..%d' % (lo, hi))
continue
return vgame_create、user_input和mines_generator方法的返回值未使用。
属性table、width、height和mine_numbers都是在构造函数和game_create方法中初始化的。最好将game_create代码放入构造函数中,以便只进行一次初始化。毕竟,它是一个构造函数,因此游戏在其中创建是有意义的。
看起来,如果一个单元格包含9,那么它就是一个地雷。最好是避免神奇的数字,而是使用常量。申报
class Minesweeper:
CLEAR = 0
MINE = 9然后检查单元格是否包含地雷写入。
self.table[i][j] == Minesweeper.MINE我不清楚为什么板表示(table)被初始化为一维数组,然后改为二维数组。为什么不从一开始就把它变成二维的?
min和max计数地雷
使用min和max以下列方式计数地雷:
def complete_table(self):
width, height, table = self.width, self.height, self.table
for i in range(height):
for j in range(width):
if table[i][j] == Minesweeper.MINE:
continue
table[i][j] = 0
for i2 in range(max(0, i - 1), min(i + 2, height)):
for j2 in range(max(0, j -1), min(j + 2, width)):
if table[i2][j2] == Minesweeper.MINE:
table[i][j] += 1min和max用于边界检查是一种常见的模式。第一行只是缩短代码,这样self就不必在任何地方重复。
如果某些输入必须以某种方式转换才能被计算机使用,那么它应该以转换后的格式存储。这样,每次使用数据时都不必重复转换。
例如,使用基于1的索引输入user_column和user_row .这很好,因为这对人类来说是有意义的。但是对于Python来说,基于0的索引更方便,所以一旦从用户读取值,就应该立即进行转换。转换仅仅意味着从它们中减去1。
user_reveal属性也是如此。它存储的序列如下:
['0101', '0201', '0404']但是它应该以Python友好的格式存储,如下所示:
[(0, 0), (1, 0), (3, 3)]user_row和user_column在第一个回合之后与user_reveal列表的最后一个元素相同。这允许我们删除user_row和user_column属性,而是通过引用user_reveal[-1]获取相同的数据。
在end_game中有以下内容:
if ...:
end_game = True
...
elif ...:
end_game = True
...
else:
end_game = False最好用以下方式来表达
if ...:
...
return True
if ...:
...
return True
return False因为它使代码流“更直”。
嵌套函数有其用途,但它们也使代码更难理解。海事组织,应该避免这样做。
最后但并非最不重要的是,许多对象的名称可以改进。对于方法和函数,最好包括一个动词来使名称“active”。以下是我建议的改名:
first_turn => run_first_turnprint_table => print_minefieldmines_generator => place_minesmine_numbers => mine_countuser_input => read_locationend_game => is_game_overuser_reveal => revealed_locations (对于集合类型,您希望名称以S结尾)adjacent_zero => reveal_safe_locationsreveal_mine => reveal_all_minescomplete_table => place_mine_countstable => minefieldimport itertools
import random
# Minesweeper
# -----------
# Minesweeper is a solitaire game in which the goal is to reveal all
# mines on a board. The Minesweeper class implements the game. It has
# the following attributes:
#
# * width: width of board
# ...
# * minefield: board representation
#
# This example shows how the class should be used:
#
# ms = Minesweeper()
# while True:
# ms.game_create()
# ...
def prompt_int(name, lo, hi):
while True:
s = input(f'Enter {name} [{lo}..{hi}]: ')
try:
v = int(s)
except ValueError:
print('ERROR: Try again, number only')
continue
if not (lo <= v <= hi):
print('ERROR: Allowed range %d..%d' % (lo, hi))
continue
return v
class Minesweeper:
CLEAR = 0
MINE = 9
def __init__(self, width = 9, height = 10, mine_count = 12):
self.revealed_locations = []
print(f'Default size is {width}*{height}, {mine_count} mines')
default_size = input('Play default size? [Y/n]: ')
if default_size.lower() == 'n':
self.width = prompt_int('width', 0, 99)
self.height = prompt_int('height', 0, 99)
self.mine_count = prompt_int('number of mines',
0, self.width * self.height - 1)
else:
self.width = width
self.height = height
self.mine_count = mine_count
self.minefield = [[Minesweeper.CLEAR] * self.width
for _ in range(self.height)]
def read_location(self):
while True:
s = input('Enter {[column][row]} in 4 digits eg. 0105: ')
if len(s) != 4:
print('ERROR: Only 4 digits allowed')
continue
try:
row = int(s[2:]) - 1
column = int(s[:2]) - 1
except ValueError:
print('ERROR: Try again, number only')
continue
if not (0 <= row < self.height):
print('ERROR: Row out of range')
elif not (0 <= column < self.width):
print('ERROR: Column of range')
elif (row, column) in self.revealed_locations:
print('ERROR: Already revealed')
else:
break
self.revealed_locations.append((row, column))
def place_mines(self):
locs = set(itertools.product(range(self.height), range(self.width)))
locs -= {self.revealed_locations[-1]}
locs = random.sample(locs, self.mine_count)
for row, column in locs:
self.minefield[row][column] = Minesweeper.MINE
def place_mine_counts(self):
width, height, minefield = self.width, self.height, self.minefield
for i in range(height):
for j in range(width):
if minefield[i][j] == Minesweeper.MINE:
continue
minefield[i][j] = Minesweeper.CLEAR
for i2 in range(max(0, i - 1), min(i + 2, height)):
for j2 in range(max(0, j -1), min(j + 2, width)):
if minefield[i2][j2] == Minesweeper.MINE:
minefield[i][j] += 1
def reveal_safe_locations(self, row, column):
width, height, minefield = self.width, self.height, self.minefield
if minefield[row][column] == Minesweeper.CLEAR:
for i in range(max(0, row - 1), min(row + 2, height)):
for j in range(max(0, column - 1), min(column + 2, width)):
if (i, j) not in self.revealed_locations:
self.revealed_locations.append((i, j))
if minefield[i][j] == Minesweeper.CLEAR:
self.reveal_safe_locations(i, j)
def run_first_turn(self):
self.read_location()
self.place_mines()
self.place_mine_counts()
row, column = self.revealed_locations[-1]
self.reveal_safe_locations(row, column)
def print_minefield(self):
print('\n'*10)
for row in range(self.height + 1):
cell = '|'
for column in range(self.width + 1):
if row == 0 and column == 0:
cell += ' .|'
elif row == 0:
cell += f'{column:2}|'
elif column == 0:
cell += f'{row:2}|'
elif (row - 1, column - 1) in self.revealed_locations:
cell += f'{self.minefield[row-1][column-1]:2}|'
else:
cell += '{:>3}'.format('|')
print(cell)
def reveal_all_mine(self):
for i in range(self.height):
for j in range(self.width):
if self.minefield[i][j] == Minesweeper.MINE:
self.minefield[i][j] = 'XX'
if (i, j) not in self.revealed_locations:
self.minefield[i][j] = '**'
self.revealed_locations.append((i, j))
def is_game_over(self):
row, column = self.revealed_locations[-1]
if self.minefield[row][column] == Minesweeper.MINE:
self.reveal_all_mines()
self.print_minefield()
print('YOU LOSE!')
return True
unmined_locations_count = self.width * self.height - self.mine_count
if len(self.revealed_locations) == unmined_locations_count:
self.reveal_all_mines()
self.print_minefield()
print('YOU WIN!')
return True
return False
def restart_game(self):
restart = input('Restart? [y/N]: ')
return restart.lower() == 'y'
def main():
while True:
ms = Minesweeper()
ms.print_minefield()
ms.run_first_turn()
while not ms.is_game_over():
ms.print_minefield()
ms.read_location()
row, column = ms.revealed_locations[-1]
ms.reveal_safe_locations(row, column)
if not ms.restart_game():
break
if __name__ == '__main__':
main()https://codereview.stackexchange.com/questions/234725
复制相似问题