Skip to content

Latest commit

 

History

History
307 lines (250 loc) · 12.7 KB

File metadata and controls

307 lines (250 loc) · 12.7 KB

三十七、饥饿机器人

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

你和饥饿的机器人被困在一个迷宫里!你不知道机器人为什么需要吃饭,但你也不想知道。机器人的程序设计很糟糕,即使被墙挡住,它们也会直接向你移动。你必须欺骗机器人互相碰撞(或死亡的机器人)而不被抓住。

你有一个个人传送装置,可以把你送到一个随机的新地方,但它的电池只够两次旅行。此外,你和机器人可以溜过角落!

运行示例

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

Hungry Robots, by Al Sweigart email@protected
`--snip--`
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░   ░ R             R ░  ░             ░
░ ░    ░░░   R░                    ░░  ░
░     ░ ░    ░ ░  ░         ░  ░░░     ░
░    R░   ░    ░      ░░   ░░     ░    ░
░ ░░  ░     ░ ░░░    ░           ░     ░
░ ░░    ░   RX░░░  ░  ░  ░      ░      ░
░          ░ R     R        R ░      ░ ░
░    ░   ░            ░        ░   R ░ ░
░ ░ R       RRR          ░
░   ░  ░     ░       ░  ░       ░   ░  ░
░  @            ░          ░    R░░░ ░ ░
░   ░  ░░      ░░                 ░    ░
░  ░   ░░  ░            ░     R       ░░
░░X          ░  ░        ░ R ░░RRR ░
░RR R       R ░    ░          ░       R░
░   ░░  RRR   R                        ░
░           ░░R     ░                  ░
░      R  ░ ░                     ░    ░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
(T)eleports remaining: 2
                    (Q) (W) ( )
                    (A) (S) (D)
Enter move or QUIT: (Z) (X) ( )
`--snip--`

工作原理

在这个游戏中代表位置的 x 和 y 笛卡尔坐标允许我们使用数学来确定机器人应该移动的方向。在编程中,x 坐标向右增加,y 坐标向下增加。这意味着如果机器人的 x 坐标大于玩家的坐标,它应该向左移动(即代码应该从其当前的 x 坐标中减去)以靠近玩家。如果机器人的 x 坐标更小,它应该向右移动(也就是说,代码应该添加到其当前的 x 坐标中)。这同样适用于基于相对 y 坐标的上下移动。

"""Hungry Robots, by Al Sweigart email@protected
Escape the hungry robots by making them crash into each other.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, game"""

import random, sys

# Set up the constants:
WIDTH = 40           # (!) Try changing this to 70 or 10.
HEIGHT = 20          # (!) Try changing this to 10.
NUM_ROBOTS = 10      # (!) Try changing this to 1 or 30.
NUM_TELEPORTS = 2    # (!) Try changing this to 0 or 9999.
NUM_DEAD_ROBOTS = 2  # (!) Try changing this to 0 or 20.
NUM_WALLS = 100      # (!) Try changing this to 0 or 300.

EMPTY_SPACE = ' '    # (!) Try changing this to '.'.
PLAYER = '@'         # (!) Try changing this to 'R'.
ROBOT = 'R'          # (!) Try changing this to '@'.
DEAD_ROBOT = 'X'     # (!) Try changing this to 'R'.

# (!) Try changing this to '#' or 'O' or ' ':
WALL = chr(9617)  # Character 9617 is '░'


def main():
   print('''Hungry Robots, by Al Sweigart email@protected

You are trapped in a maze with hungry robots! You don't know why robots
need to eat, but you don't want to find out. The robots are badly
programmed and will move directly toward you, even if blocked by walls.
You must trick the robots into crashing into each other (or dead robots)
without being caught. You have a personal teleporter device, but it only
has enough battery for {} trips. Keep in mind, you and robots can slip
through the corners of two diagonal walls!
'''.format(NUM_TELEPORTS))

   input('Press Enter to begin...')

   # Set up a new game:
   board = getNewBoard()
   robots = addRobots(board)
   playerPosition = getRandomEmptySpace(board, robots)
   while True:  # Main game loop.
       displayBoard(board, robots, playerPosition)

       if len(robots) == 0:  # Check if the player has won.
           print('All the robots have crashed into each other and you')
           print('lived to tell the tale! Good job!')
           sys.exit()

       # Move the player and robots:
       playerPosition = askForPlayerMove(board, robots, playerPosition)
       robots = moveRobots(board, robots, playerPosition)

       for x, y in robots:  # Check if the player has lost.
           if (x, y) == playerPosition:
               displayBoard(board, robots, playerPosition)
               print('You have been caught by a robot!')
               sys.exit()


def getNewBoard():
   """Returns a dictionary that represents the board. The keys are
   (x, y) tuples of integer indexes for board positions, the values are
   WALL, EMPTY_SPACE, or DEAD_ROBOT. The dictionary also has the key
   'teleports' for the number of teleports the player has left.
   The living robots are stored separately from the board dictionary."""
   board = {'teleports': NUM_TELEPORTS}

   # Create an empty board:
   for x in range(WIDTH):
       for y in range(HEIGHT):
           board[(x, y)] = EMPTY_SPACE

   # Add walls on the edges of the board:
   for x in range(WIDTH):
       board[(x, 0)] = WALL  # Make top wall.
       board[(x, HEIGHT - 1)] = WALL  # Make bottom wall.
   for y in range(HEIGHT):
       board[(0, y)] = WALL  # Make left wall.
       board[(WIDTH - 1, y)] = WALL  # Make right wall.

   # Add the random walls:
   for i in range(NUM_WALLS):
       x, y = getRandomEmptySpace(board, [])
       board[(x, y)] = WALL

   # Add the starting dead robots:
   for i in range(NUM_DEAD_ROBOTS):
       x, y = getRandomEmptySpace(board, [])
       board[(x, y)] = DEAD_ROBOT
   return board


def getRandomEmptySpace(board, robots):
   """Return a (x, y) integer tuple of an empty space on the board."""
   while True:
       randomX = random.randint(1, WIDTH - 2)
       randomY = random.randint(1, HEIGHT - 2)
        if isEmpty(randomX, randomY, board, robots):
            break
    return (randomX, randomY)


def isEmpty(x, y, board, robots):
    """Return True if the (x, y) is empty on the board and there's also
    no robot there."""
    return board[(x, y)] == EMPTY_SPACE and (x, y) not in robots


def addRobots(board):
    """Add NUM_ROBOTS number of robots to empty spaces on the board and
    return a list of these (x, y) spaces where robots are now located."""
    robots = []
    for i in range(NUM_ROBOTS):
        x, y = getRandomEmptySpace(board, robots)
        robots.append((x, y))
    return robots


def displayBoard(board, robots, playerPosition):
    """Display the board, robots, and player on the screen."""
    # Loop over every space on the board:
    for y in range(HEIGHT):
        for x in range(WIDTH):
            # Draw the appropriate character:
            if board[(x, y)] == WALL:
                print(WALL, end='')
            elif board[(x, y)] == DEAD_ROBOT:
                print(DEAD_ROBOT, end='')
            elif (x, y) == playerPosition:
                print(PLAYER, end='')
            elif (x, y) in robots:
                print(ROBOT, end='')
            else:
                print(EMPTY_SPACE, end='')
        print()  # Print a newline.


def askForPlayerMove(board, robots, playerPosition):
    """Returns the (x, y) integer tuple of the place the player moves
    next, given their current location and the walls of the board."""
    playerX, playerY = playerPosition

    # Find which directions aren't blocked by a wall:
    q = 'Q' if isEmpty(playerX - 1, playerY - 1, board, robots) else ' '
    w = 'W' if isEmpty(playerX + 0, playerY - 1, board, robots) else ' '
    e = 'E' if isEmpty(playerX + 1, playerY - 1, board, robots) else ' '
    d = 'D' if isEmpty(playerX + 1, playerY + 0, board, robots) else ' '
    c = 'C' if isEmpty(playerX + 1, playerY + 1, board, robots) else ' '
    x = 'X' if isEmpty(playerX + 0, playerY + 1, board, robots) else ' '
    z = 'Z' if isEmpty(playerX - 1, playerY + 1, board, robots) else ' '
    a = 'A' if isEmpty(playerX - 1, playerY + 0, board, robots) else ' '
    allMoves = (q + w + e + d + c + x + a + z + 'S')

    while True:
        # Get player's move:
        print('(T)eleports remaining: {}'.format(board["teleports"]))
        print('                    ({}) ({}) ({})'.format(q, w, e))
        print('                    ({}) (S) ({})'.format(a, d))
        print('Enter move or QUIT: ({}) ({}) ({})'.format(z, x, c))

        move = input('> ').upper()
        if move == 'QUIT':
            print('Thanks for playing!')
            sys.exit()
        elif move == 'T' and board['teleports'] > 0:
            # Teleport the player to a random empty space:
            board['teleports'] -= 1
            return getRandomEmptySpace(board, robots)
        elif move != '' and move in allMoves:
            # Return the new player position based on their move:
            return {'Q': (playerX - 1, playerY - 1),
                    'W': (playerX + 0, playerY - 1),
                    'E': (playerX + 1, playerY - 1),
                    'D': (playerX + 1, playerY + 0),
                    'C': (playerX + 1, playerY + 1),
                    'X': (playerX + 0, playerY + 1),
                    'Z': (playerX - 1, playerY + 1),
                    'A': (playerX - 1, playerY + 0),
                    'S': (playerX, playerY)}[move]


def moveRobots(board, robotPositions, playerPosition):
    """Return a list of (x, y) tuples of new robot positions after they
    have tried to move toward the player."""
    playerx, playery = playerPosition
    nextRobotPositions = []

    while len(robotPositions) > 0:
        robotx, roboty = robotPositions[0]

        # Determine the direction the robot moves.
        if robotx < playerx:
            movex = 1  # Move right.
        elif robotx > playerx:
            movex = -1  # Move left.
        elif robotx == playerx:
            movex = 0  # Don't move horizontally.

        if roboty < playery:
            movey = 1  # Move up.
        elif roboty > playery:
            movey = -1  # Move down.
        elif roboty == playery:
            movey = 0  # Don't move vertically.

        # Check if the robot would run into a wall, and adjust course:
        if board[(robotx + movex, roboty + movey)] == WALL:
            # Robot would run into a wall, so come up with a new move:
            if board[(robotx + movex, roboty)] == EMPTY_SPACE:
                movey = 0  # Robot can't move horizontally.
            elif board[(robotx, roboty + movey)] == EMPTY_SPACE:
                movex = 0  # Robot can't move vertically.
            else:
                # Robot can't move.
                movex = 0
                movey = 0
        newRobotx = robotx + movex
        newRoboty = roboty + movey

        if (board[(robotx, roboty)] == DEAD_ROBOT
            or board[(newRobotx, newRoboty)] == DEAD_ROBOT):
            # Robot is at a crash site, remove it.
            del robotPositions[0]
            continue

        # Check if it moves into a robot, then destroy both robots:
        if (newRobotx, newRoboty) in nextRobotPositions:
            board[(newRobotx, newRoboty)] = DEAD_ROBOT
            nextRobotPositions.remove((newRobotx, newRoboty))
        else:
            nextRobotPositions.append((newRobotx, newRoboty))

        # Remove robots from robotPositions as they move.
        del robotPositions[0]
    return nextRobotPositions


# If this program was run (instead of imported), run the game:
if __name__ == '__main__':
    main() 

在输入源代码并运行几次之后,尝试对其进行实验性的修改。标有(!)的注释对你可以做的小改变有建议。你也可以自己想办法做到以下几点:

  • 创造两种不同的机器人:只能沿对角线移动的机器人和只能沿基本方向移动的机器人。
  • 给玩家一定数量的陷阱,他们可以留下来阻止任何机器人踩到陷阱。
  • 给玩家有限数量的“瞬间墙”,他们可以建立自己的防御。

探索程序

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

  1. 如果把第 22 行的WALL = chr(9617)改成WALL = 'R'会怎么样?
  2. 如果把 237 行的return nextRobotPositions改成return robotPositions会怎么样?
  3. 如果删除或注释掉第 44 行的displayBoard(board, robots, playerPosition)会发生什么?
  4. 如果删除或注释掉第 53 行的robots = moveRobots(board, robots, playerPosition)会发生什么?