首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在一系列动作后确定俄罗斯方块棋盘的高度

在一系列动作后确定俄罗斯方块棋盘的高度
EN

Code Review用户
提问于 2020-07-22 14:38:16
回答 2查看 581关注 0票数 6

我已经写了一个程序,将确定一个俄罗斯方块板的高度后,一系列的移动是作出。这些输入以逗号分隔列表的形式出现,类似于<piece><position>。作品清单:

  • I -这是一个1x4的部件躺在它的旁边。
  • Q -这是一个2x2正方形
  • T -这是一个T形的零件
  • Z -这是一个左向2x2偏移量。
  • S -这是一个向右的2x2偏移量。
  • L -这是一个右脸的L
  • J -这是一个左面L

图像(来源)的片段。作品的方向总是和下面一样。

我也把它们画在下面了。对于此问题,旋转不在范围内(例如,垂直I超出了范围)。

代码语言:javascript
复制
I - xxxx
Q - xx
    xx
T - xxx
     x
Z - xx
     xx
S -  xx
    xx
L - x
    x
    xx
J -  x
     x
    xx

位置是0索引,并代表一个位置从左侧的董事会(董事会是10宽)。

示例1:

输入:I0,Q4

输出:2

董事会:

代码语言:javascript
复制
bbbbQQbbbb
IIIIQQbbbb

(b表示一个空白,并省略了上面的空行)

示例2

输入:Q0,Q2,Q4,Q6,Q8

产出:0

板(故意留空):

代码语言:javascript
复制

说明:使用普通的俄罗斯方块规则,只要一行中的每一个块都被填充,一行就会被移除。这个序列将放置5个方形立方体沿底部均匀地间隔,然后移除这两行。

代码语言:javascript
复制
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)
EN

回答 2

Code Review用户

发布于 2020-07-22 19:01:38

我做的第一件事是使用黑色重新格式化代码--您的代码很好,但是我也有一些小的风格抱怨(通常是关于在几个地方缺少空白)。此外,PEP8在python中定义了命名约定--通常是prefer_this notThis

最后,所有的方法都应该有docstring。我还没有添加这个b/c --它与代码审查没有那么相关,但总的来说,它是一个很好的实践。

从那以后,我想到了你的实际做法。在高层次上,你:

  • 创建对象的新实例
  • 传递一个字符串,解析该字符串,并处理每个令牌
  • 试着把零件装好
  • 清整行

这些都不是本质上的坏处,但我认为可以收紧一点。

用户输入

现在您还没有对用户输入进行任何验证--我们非常信任所提供的值将是可用的。我们可能想要做这个验证

此外,我不认为Tetris类应该负责处理以逗号分隔的字符串--它应该只占一个片段和一个位置,而其他的类应该负责接收输入并将其转换为参数。如果您感到友好,@classmethod可能是合适的。最后,我认为这个类方法应该返回板,而不是高度,所以我给类添加了一个新的height属性。最后我得到了这样的结果:

代码语言:javascript
复制
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.FULLboardState.EMPTY)。最后,我从嵌套列表更改为嵌套元组--这是因为元组是不可变的,而且您永远不需要更改形状定义。

OOP

我想知道是否值得创建一个单独的类来表示这些片段,然后您可以做一些类似于TetrisPiece.fitsAtLocation(board, location)的事情。我还没有充分考虑这会是什么样子,或者它是否真的更好,但它可能是封装该功能的一个好方法。

这也是一种方便的方法,可以将其扩展到处理旋转,因为您只需执行TetrisPiece.rotate(Direction.LEFT)并在引擎盖下处理所有这些。

如果你想把它扩展到一个完整的游戏,那么你也需要一个相对位置,处理T旋转等等,而不是仅仅有一个“下降位置”,这变得越复杂,我认为一个单独的类将更好地提高可读性。

通用挑剔

  • doesThePieceFit看起来真的很奇怪--我知道它是如何工作的,但是您肯定应该引入一些常量来代替神奇的方法,也许可以考虑是否有更好的方法来建模数据。
    • 特别是,也许我们应该以相反的顺序存储不同形状的块状态(例如,自下而上而不是自上而下)?

  • removeFullRows创建一个列表,然后对其进行排序--我认为您可能会想出一种不同的方法来解决这个问题。
  • addPieceAtdoesThePieceFit有着同样的魔力--我们是否可以结合它们的功能,或者使用公共的助手方法呢?
  • addPiece我想你可以用for-else来处理这件事比使用三元更优雅,但是每次使用它时,我在for-else上的心情都会波动。
票数 2
EN

Code Review用户

发布于 2020-07-22 19:12:56

您的代码是好的,但它是不直观的接口与随意。

我可以打印板,但它倒出来,作为零和1,我必须这样做:

代码语言:javascript
复制
>>> t = Tetris()
>>> print(t.board)

但是您可以使用特殊的方法repr使其自动打印(每当用户要求print(t))。

在Python 3中,您只需在类的末尾添加以下内容:

代码语言:javascript
复制
class Tetris:
    # other code

    def __repr__(self):
        return '\n'.join(reversed([''.join("■" if elem else '□' for elem in line) for line in t.board]))

现在,你有了一个直观的,图形上漂亮的打印:

代码语言:javascript
复制
t = Tetris()
for piece, pos in ( ('L',1), ('Z', 2), ('S', 3), ('I',5)):
    t.addPiece(piece, pos)
    print(t)
    print("\n"*5)

产出:

代码语言:javascript
复制
□■□□□□□□□□
□■□□□□□□□□
□■■□□□□□□□







□■□□□□□□□□
□■■■□□□□□□
□■■■■□□□□□






□□□□■■□□□□
□■□■■□□□□□
□■■■□□□□□□
□■■■■□□□□□






□□□□□■■■■□
□□□□■■□□□□
□■□■■□□□□□
□■■■□□□□□□
□■■■■□□□□□

在Python2中,您可能必须使用ASCII字符,但是这样可以方便地进行开发和测试,并且是必要的,以防您想要将其转换为游戏。

(在Python空闲环境中,它看起来比在这个站点中要好得多)。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/245876

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档