我已经写了一个程序,将确定一个俄罗斯方块板的高度后,一系列的移动是作出。这些输入以逗号分隔列表的形式出现,类似于<piece><position>
。作品清单:
I
-这是一个1x4的部件躺在它的旁边。Q
-这是一个2x2正方形T
-这是一个T形的零件Z
-这是一个左向2x2偏移量。S
-这是一个向右的2x2偏移量。L
-这是一个右脸的LJ
-这是一个左面L图像(来源)的片段。作品的方向总是和下面一样。
我也把它们画在下面了。对于此问题,旋转不在范围内(例如,垂直I
超出了范围)。
I - xxxx
Q - xx
xx
T - xxx
x
Z - xx
xx
S - xx
xx
L - x
x
xx
J - x
x
xx
位置是0索引,并代表一个位置从左侧的董事会(董事会是10宽)。
输入:I0,Q4
输出:2
董事会:
bbbbQQbbbb
IIIIQQbbbb
(b
表示一个空白,并省略了上面的空行)
输入:Q0,Q2,Q4,Q6,Q8
产出:0
板(故意留空):
说明:使用普通的俄罗斯方块规则,只要一行中的每一个块都被填充,一行就会被移除。这个序列将放置5个方形立方体沿底部均匀地间隔,然后移除这两行。
class Tetris:
def __init__(self):
self.board =[]
self.pieces = {
'I' : [[1,1,1,1]],
'Q' : [[1,1],
[1,1]],
'T': [[1,1,1],
[0,1,0]],
'Z':[[1,1,0],
[0,1,1]],
'S':[[0,1,1],
[1,1,0]],
'L':[[1,0],
[1,0],
[1,1]],
'J':[[0,1],
[0,1],
[1,1]]}
def newRow(self):
return [0 for _ in range(10)]
def doesThePieceFit(self,row,pieceName,pos):
#checks to see if a piece fits on the row at given position
#check bottom to the top
piece = self.pieces[pieceName]
for i in range(len(piece)):
pieceRow = piece[-1*(1+i)]
if i+row == len(self.board): return True
boardRow = self.board[i+row]
for j in range(len(pieceRow)):
if pieceRow[j] and boardRow[pos+j]: return False
return True
def removeFullRows(self,startRow,numRows):
#removes full rows from the board
#only checks rows between startRow and startRow+numRows
fullRows = [i+startRow
for i in range(numRows)
if all(self.board[i+startRow])]
for fullRow in sorted(fullRows,reverse=True):
del self.board[fullRow]
def addPieceAt(self,row,pieceName,pos):
#Adds piece at this row.
piece = self.pieces[pieceName]
for i in range(len(piece)):
pieceRow = piece[-1*(1+i)]
if i+row == len(self.board):
self.board+=self.newRow(),
boardRow = self.board[i+row]
for j in range(len(pieceRow)):
if pieceRow[j]:
boardRow[pos+j] = pieceRow[j]
self.removeFullRows(row,len(piece))
def addPiece(self,pieceName,pos):
#1.find the first row where piece is blocked
#2.Add the piece at the row above it
blockedByRow = None
for row in range(len(self.board)-1,-1,-1):
if not self.doesThePieceFit(row,pieceName,pos):
blockedByRow = row
break
targetRow = 0 if blockedByRow == None else blockedByRow+1
self.addPieceAt(targetRow,pieceName,pos)
def addPieces(self,pieces):
for piece in pieces.split(','):
self.addPiece(piece[0],int(piece[1]))
return len(self.board)
发布于 2020-07-22 19:01:38
我做的第一件事是使用黑色重新格式化代码--您的代码很好,但是我也有一些小的风格抱怨(通常是关于在几个地方缺少空白)。此外,PEP8在python中定义了命名约定--通常是prefer_this
notThis
。
最后,所有的方法都应该有docstring。我还没有添加这个b/c --它与代码审查没有那么相关,但总的来说,它是一个很好的实践。
从那以后,我想到了你的实际做法。在高层次上,你:
这些都不是本质上的坏处,但我认为可以收紧一点。
现在您还没有对用户输入进行任何验证--我们非常信任所提供的值将是可用的。我们可能想要做这个验证
此外,我不认为Tetris
类应该负责处理以逗号分隔的字符串--它应该只占一个片段和一个位置,而其他的类应该负责接收输入并将其转换为参数。如果您感到友好,@classmethod
可能是合适的。最后,我认为这个类方法应该返回板,而不是高度,所以我给类添加了一个新的height
属性。最后我得到了这样的结果:
pieces = {
"I": ((True, True, True, True)),
"Q": ((True, True), (True, True)),
"T": ((True, True, True), (False, True, False)),
"Z": ((True, True, False), (False, True, True)),
"S": ((False, True, True), (True, True, False)),
"L": ((True, False), (True, False), (True, True)),
"J": ((False, True), (False, True), (True, True)),
}
@classmethod
def add_pieces(cls, user_input):
board = Tetris()
for piece in user_input.split(","):
if len(piece) > 2:
raise ValueError(f"Piece {piece} is malformed")
piece_id = piece[0]
drop_position = piece[1]
if not Tetris.is_valid_piece(piece_id):
raise ValueError(f"Piece {piece_id} is not a valid Tetris piece")
if not Tetris.is_valid_drop_location(drop_position):
raise IndexError(
f"Drop location {drop_position} is not a valid board location"
)
board.add_piece(piece_id, drop_position)
return board
@classmethod
def is_valid_piece(cls, piece_id):
return piece_id in cls.pieces
@classmethod
def is_valid_drop_location(drop_position):
try:
int(drop_position)
except ValueError:
return False
return drop_position >= 0 and drop_position < 10
@property
def height(self):
return self.board.length
您还会注意到,我将Tetris.pieces
移到了一个类属性中,而不是一个实例属性--这是因为它在任何地方都应该是相同的。我还将0/1
更改为True/False
,因为它是一个二进制值(我认为enum
最好是显式的,例如boardState.FULL
和boardState.EMPTY
)。最后,我从嵌套列表更改为嵌套元组--这是因为元组是不可变的,而且您永远不需要更改形状定义。
我想知道是否值得创建一个单独的类来表示这些片段,然后您可以做一些类似于TetrisPiece.fitsAtLocation(board, location)
的事情。我还没有充分考虑这会是什么样子,或者它是否真的更好,但它可能是封装该功能的一个好方法。
这也是一种方便的方法,可以将其扩展到处理旋转,因为您只需执行TetrisPiece.rotate(Direction.LEFT)
并在引擎盖下处理所有这些。
如果你想把它扩展到一个完整的游戏,那么你也需要一个相对位置,处理T旋转等等,而不是仅仅有一个“下降位置”,这变得越复杂,我认为一个单独的类将更好地提高可读性。
doesThePieceFit
看起来真的很奇怪--我知道它是如何工作的,但是您肯定应该引入一些常量来代替神奇的方法,也许可以考虑是否有更好的方法来建模数据。removeFullRows
创建一个列表,然后对其进行排序--我认为您可能会想出一种不同的方法来解决这个问题。addPieceAt
和doesThePieceFit
有着同样的魔力--我们是否可以结合它们的功能,或者使用公共的助手方法呢?addPiece
我想你可以用for-else
来处理这件事比使用三元更优雅,但是每次使用它时,我在for-else
上的心情都会波动。发布于 2020-07-22 19:12:56
您的代码是好的,但它是不直观的接口与随意。
我可以打印板,但它倒出来,作为零和1,我必须这样做:
>>> t = Tetris()
>>> print(t.board)
但是您可以使用特殊的方法repr
使其自动打印(每当用户要求print(t)
)。
在Python 3中,您只需在类的末尾添加以下内容:
class Tetris:
# other code
def __repr__(self):
return '\n'.join(reversed([''.join("■" if elem else '□' for elem in line) for line in t.board]))
现在,你有了一个直观的,图形上漂亮的打印:
t = Tetris()
for piece, pos in ( ('L',1), ('Z', 2), ('S', 3), ('I',5)):
t.addPiece(piece, pos)
print(t)
print("\n"*5)
产出:
□■□□□□□□□□
□■□□□□□□□□
□■■□□□□□□□
□■□□□□□□□□
□■■■□□□□□□
□■■■■□□□□□
□□□□■■□□□□
□■□■■□□□□□
□■■■□□□□□□
□■■■■□□□□□
□□□□□■■■■□
□□□□■■□□□□
□■□■■□□□□□
□■■■□□□□□□
□■■■■□□□□□
在Python2中,您可能必须使用ASCII字符,但是这样可以方便地进行开发和测试,并且是必要的,以防您想要将其转换为游戏。
(在Python空闲环境中,它看起来比在这个站点中要好得多)。
https://codereview.stackexchange.com/questions/245876
复制相似问题