Skip to content

Latest commit

 

History

History
311 lines (250 loc) · 11.3 KB

File metadata and controls

311 lines (250 loc) · 11.3 KB

七十九、2048

原文:http://inventwithpython.com/bigbookpython/project79.html

网络开发者 Gabriele Cirulli 在一个周末就发明了游戏 2048。它的灵感来自于 Veewo 工作室的 1024 游戏,而这个游戏的灵感又来自于 Threes!,开发团队 Sirvo 的一款游戏。2048,你必须在一个4 × 4的板上合并数字,才能把它们从屏幕上清除。两个 2 合并成一个 4,两个 4 合并成一个 8,以此类推。每次合并时,游戏都会在棋盘上添加一个新的 2。目标是在整个董事会填满之前达到 2048。

运行示例

当您运行twentyfortyeight.py时,输出将如下所示:

Twenty Forty-Eight, by Al Sweigart email@protected
`--snip--`
+-----+-----+-----+-----+
|     |     |     |     |
|     |     |  2  |  16 |
|     |     |     |     |
+-----+-----+-----+-----+
|     |     |     |     |
|     |  16 |  4  |  2  |
|     |     |     |     |
+-----+-----+-----+-----+
|     |     |     |     |
|  2  |     |  4  |  32 |
|     |     |     |     |
+-----+-----+-----+-----+
|     |     |     |     |
|     |     |     |  2  |
|     |     |     |     |
+-----+-----+-----+-----+

Score: 80
Enter move: (WASD or Q to quit)
`--snip--`

工作原理

这个程序使用“列”数据结构实现它的滑动行为,由四个字符串列表表示:BLANK(一个单空格字符串)、'2''4''8'等等。该列表中的第一个值表示列的底部,而最后一个值表示列的顶部。无论玩家向上、向下、向左或向右滑动牌,组合在一列中的数字总是向下滑动。想象一下重力将瓷砖拉向这些方向。例如,图 79-1 显示了一块向右滑动的棋盘。我们将创建四个列表来表示列:

  • ['2', '4', '8', ' ']
  • [' ', ' ', ' ', '4']
  • [' ', ' ', ' ', '2']
  • [' ', ' ', ' ', ' ']

combineTilesInColumn()函数接受一个列列表并返回另一个列列表,匹配的数字被组合并向底部移动。调用combineTilesInColumn()的代码负责在适当的方向创建列列表,并用返回的列表更新游戏板。

f79001

:游戏板向右滑动时的列(高亮显示)

"""Twenty Forty-Eight, by Al Sweigart email@protected
A sliding tile game to combine exponentially-increasing numbers.
Inspired by Gabriele Cirulli's 2048, which is a clone of Veewo Studios'
1024, which in turn is a clone of the Threes! game.
More info at https://en.wikipedia.org/wiki/2048_(video_game)
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, game, puzzle"""

import random, sys

# Set up the constants:
BLANK = ''  # A value that represents a blank space on the board.


def main():
   print('''Twenty Forty-Eight, by Al Sweigart email@protected

Slide all the tiles on the board in one of four directions. Tiles with
like numbers will combine into larger-numbered tiles. A new 2 tile is
added to the board on each move. You win if you can create a 2048 tile.
You lose if the board fills up the tiles before then.''')
   input('Press Enter to begin...')

   gameBoard = getNewBoard()

   while True:  # Main game loop.
       drawBoard(gameBoard)
       print('Score:', getScore(gameBoard))
       playerMove = askForPlayerMove()
       gameBoard = makeMove(gameBoard, playerMove)
       addTwoToBoard(gameBoard)

       if isFull(gameBoard):
           drawBoard(gameBoard)
           print('Game Over - Thanks for playing!')
           sys.exit()


def getNewBoard():
   """Returns a new data structure that represents a board.

   It's a dictionary with keys of (x, y) tuples and values of the tile
   at that space. The tile is either a power-of-two integer or BLANK.
   The coordinates are laid out as:
      X0 1 2 3
     Y+-+-+-+-+
     0| | | | |
      +-+-+-+-+
     1| | | | |
      +-+-+-+-+
     2| | | | |
      +-+-+-+-+
     3| | | | |
      +-+-+-+-+"""

   newBoard = {}  # Contains the board data structure to be returned.
   # Loop over every possible space and set all the tiles to blank:
   for x in range(4):
       for y in range(4):
           newBoard[(x, y)] = BLANK

   # Pick two random spaces for the two starting 2's:
   startingTwosPlaced = 0  # The number of starting spaces picked.
   while startingTwosPlaced < 2:  # Repeat for duplicate spaces.
       randomSpace = (random.randint(0, 3), random.randint(0, 3))
       # Make sure the randomly selected space isn't already taken:
       if newBoard[randomSpace] == BLANK:
           newBoard[randomSpace] = 2
           startingTwosPlaced = startingTwosPlaced + 1

   return newBoard


def drawBoard(board):
   """Draws the board data structure on the screen."""

   # Go through each possible space left to right, top to bottom, and
   # create a list of what each space's label should be.
   labels = []  # A list of strings for the number/blank for that tile.
   for y in range(4):
       for x in range(4):
           tile = board[(x, y)]  # Get the tile at this space.
           # Make sure the label is 5 spaces long:
           labelForThisTile = str(tile).center(5)
           labels.append(labelForThisTile)

   # The {} are replaced with the label for that tile:
   print("""
+-----+-----+-----+-----+
|     |     |     |     |
|{}|{}|{}|{}|
|     |     |     |     |
+-----+-----+-----+-----+
|     |     |     |     |
|{}|{}|{}|{}|
|     |     |     |     |
+-----+-----+-----+-----+
|     |     |     |     |
|{}|{}|{}|{}|
|     |     |     |     |
+-----+-----+-----+-----+
|     |     |     |     |
|{}|{}|{}|{}|
|     |     |     |     |
+-----+-----+-----+-----+
""".format(*labels))


def getScore(board):
    """Returns the sum of all the tiles on the board data structure."""
    score = 0
    # Loop over every space and add the tile to the score:
    for x in range(4):
        for y in range(4):
            # Only add non-blank tiles to the score:
            if board[(x, y)] != BLANK:
                score = score + board[(x, y)]
    return score


def combineTilesInColumn(column):
    """The column is a list of four tile. Index 0 is the "bottom" of
    the column, and tiles are pulled "down" and combine if they are the
    same. For example, combineTilesInColumn([2, BLANK, 2, BLANK])
    returns [4, BLANK, BLANK, BLANK]."""

    # Copy only the numbers (not blanks) from column to combinedTiles
    combinedTiles = []  # A list of the non-blank tiles in column.
    for i in range(4):
        if column[i] != BLANK:
            combinedTiles.append(column[i])

    # Keep adding blanks until there are 4 tiles:
    while len(combinedTiles) < 4:
        combinedTiles.append(BLANK)

    # Combine numbers if the one "above" it is the same, and double it.
    for i in range(3):  # Skip index 3: it's the topmost space.
        if combinedTiles[i] == combinedTiles[i + 1]:
            combinedTiles[i] *= 2  # Double the number in the tile.
            # Move the tiles above it down one space:
            for aboveIndex in range(i + 1, 3):
                combinedTiles[aboveIndex] = combinedTiles[aboveIndex + 1]
            combinedTiles[3] = BLANK  # Topmost space is always BLANK.
    return combinedTiles


def makeMove(board, move):
    """Carries out the move on the board.

    The move argument is either 'W', 'A', 'S', or 'D' and the function
    returns the resulting board data structure."""

    # The board is split up into four columns, which are different
    # depending on the direction of the move:
    if move == 'W':
        allColumnsSpaces = [[(0, 0), (0, 1), (0, 2), (0, 3)],
                            [(1, 0), (1, 1), (1, 2), (1, 3)],
                            [(2, 0), (2, 1), (2, 2), (2, 3)],
                            [(3, 0), (3, 1), (3, 2), (3, 3)]]
    elif move == 'A':
        allColumnsSpaces = [[(0, 0), (1, 0), (2, 0), (3, 0)],
                            [(0, 1), (1, 1), (2, 1), (3, 1)],
                            [(0, 2), (1, 2), (2, 2), (3, 2)],
                            [(0, 3), (1, 3), (2, 3), (3, 3)]]
    elif move == 'S':
        allColumnsSpaces = [[(0, 3), (0, 2), (0, 1), (0, 0)],
                            [(1, 3), (1, 2), (1, 1), (1, 0)],
                            [(2, 3), (2, 2), (2, 1), (2, 0)],
                            [(3, 3), (3, 2), (3, 1), (3, 0)]]
    elif move == 'D':
        allColumnsSpaces = [[(3, 0), (2, 0), (1, 0), (0, 0)],
                            [(3, 1), (2, 1), (1, 1), (0, 1)],
                            [(3, 2), (2, 2), (1, 2), (0, 2)],
                            [(3, 3), (2, 3), (1, 3), (0, 3)]]

    # The board data structure after making the move:
    boardAfterMove = {}
    for columnSpaces in allColumnsSpaces:  # Loop over all 4 columns.
        # Get the tiles of this column (The first tile is the "bottom"
        # of the column):
        firstTileSpace = columnSpaces[0]
        secondTileSpace = columnSpaces[1]
        thirdTileSpace = columnSpaces[2]
        fourthTileSpace = columnSpaces[3]

        firstTile = board[firstTileSpace]
        secondTile = board[secondTileSpace]
        thirdTile = board[thirdTileSpace]
        fourthTile = board[fourthTileSpace]

        # Form the column and combine the tiles in it:
        column = [firstTile, secondTile, thirdTile, fourthTile]
        combinedTilesColumn = combineTilesInColumn(column)

        # Set up the new board data structure with the combined tiles:
        boardAfterMove[firstTileSpace] = combinedTilesColumn[0]
        boardAfterMove[secondTileSpace] = combinedTilesColumn[1]
        boardAfterMove[thirdTileSpace] = combinedTilesColumn[2]
        boardAfterMove[fourthTileSpace] = combinedTilesColumn[3]

    return boardAfterMove


def askForPlayerMove():
    """Asks the player for the direction of their next move (or quit).

    Ensures they enter a valid move: either 'W', 'A', 'S' or 'D'."""
    print('Enter move: (WASD or Q to quit)')
    while True:  # Keep looping until they enter a valid move.
        move = input('> ').upper()
        if move == 'Q':
            # End the program:
            print('Thanks for playing!')
            sys.exit()

        # Either return the valid move, or loop back and ask again:
        if move in ('W', 'A', 'S', 'D'):
            return move
        else:
            print('Enter one of "W", "A", "S", "D", or "Q".')


def addTwoToBoard(board):
    """Adds a new 2 tile randomly to the board."""
    while True:
        randomSpace = (random.randint(0, 3), random.randint(0, 3))
        if board[randomSpace] == BLANK:
            board[randomSpace] = 2
            return  # Return after finding one non-blank tile.


def isFull(board):
    """Returns True if the board data structure has no blanks."""
    # Loop over every space on the board:
    for x in range(4):
        for y in range(4):
            # If a space is blank, return False:
            if board[(x, y)] == BLANK:
                return False
    return True  # No space is blank, so return True.


# If this program was run (instead of imported), run the game:
if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        sys.exit()  # When Ctrl-C is pressed, end the program. 

探索程序

试着找出下列问题的答案。尝试对代码进行一些修改,然后重新运行程序,看看这些修改有什么影响。

  1. 如果把 118 行的return score改成return 9999会怎么样?
  2. 如果把 229 行的board[randomSpace] = 2改成board[randomSpace] = 256会怎么样?