欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 文化 > 【Dison夏令营 Day 13】使用 Python 创建扫雷游戏

【Dison夏令营 Day 13】使用 Python 创建扫雷游戏

2024/11/30 6:48:24 来源:https://blog.csdn.net/weixin_41446370/article/details/140276498  浏览:    关键词:【Dison夏令营 Day 13】使用 Python 创建扫雷游戏

在本文中,我们将介绍如何使用 Python 语言创建自己的基于终端的扫雷程序。

关于游戏
1992年4月6日,扫雷和纸牌、空当接龙等小游戏搭载在Windows 3.1系统中与用户见面,主要目的是让用户训练使用鼠标。扫雷是一款单人游戏,这个游戏的玩法很简单,有初级、中级、高级和自定义等模式,雷区中随机布置一定数量的地雷,玩家需要清除一个包含地雷和数字的正方形网格。玩家需要借助相邻方格中的数字来防止自己落在地雷上,但不许踩到地雷。

在这里插入图片描述

使用 Python 设计扫雷游戏

在创建游戏逻辑之前,我们需要设计游戏的基本布局。使用 Python 创建正方形网格非常容易:

# Printing the Minesweeper Layout
def print_mines_layout():global mine_valuesglobal nprint()print("\t\t\tMINESWEEPER\n")st = "   "for i in range(n):st = st + "     " + str(i + 1)print(st)   for r in range(n):st = "     "if r == 0:for col in range(n):st = st + "______" print(st)st = "     "for col in range(n):st = st + "|     "print(st + "|")st = "  " + str(r + 1) + "  "for col in range(n):st = st + "|  " + str(mine_values[r][col]) + "  "print(st + "|") st = "     "for col in range(n):st = st + "|_____"print(st + '|')print()

每次迭代显示的网格如下图所示:

在这里插入图片描述
M "符号表示该单元格中存在 “地雷”。我们可以清楚地看到,网格上的任何数字都表示相邻 "8 "单元格中存在的地雷数量。

本教程将进一步解释如何使用 mine_values 等变量。

输入系统

任何游戏最重要的部分之一就是输入法。在我们的扫雷版本中,我们将使用行数和列数作为输入技术。

在开始游戏之前,脚本必须为玩家提供一组指令。我们的游戏会打印以下内容。

在这里插入图片描述
与网格一起显示的行数和列数对我们的输入系统很有帮助。我们知道,在没有任何指示器的情况下追踪地雷是很困难的。因此,扫雷游戏提供了一种使用 "标志 "来标记我们已知含有地雷的单元格的方法。

数据存储

对于一局扫雷游戏,我们需要记录以下信息:

  • 网格大小
  • 地雷数量
  • 实际 "网格值——在游戏开始时,我们需要一个容器来存储玩家未知的游戏实际值。例如,地雷的位置。
  • 表面 "网格值 - 每次移动后,我们都需要更新所有必须显示给玩家的值。
  • 标记位置 - 已标记的单元格。

这些值通过以下数据结构存储

if __name__ == "__main__":# Size of gridn = 8# Number of minesmines_no = 8# The actual values of the gridnumbers = [[0 for y in range(n)] for x in range(n)] # The apparent values of the gridmine_values = [[' ' for y in range(n)] for x in range(n)]# The positions that have been flaggedflags = []

扫雷的游戏逻辑并不复杂。所有的努力都是为了设置扫雷布局。

设置地雷

我们需要随机设置地雷的位置,这样玩家就无法预测它们的位置。这可以通过以下方法实现

# Function for setting up Mines
def set_mines():global numbersglobal mines_noglobal n# Track of number of mines already set upcount = 0while count < mines_no:# Random number from all possible grid positions val = random.randint(0, n*n-1)# Generating row and column from the numberr = val // ncol = val % n# Place the mine, if it doesn't already have oneif numbers[r][col] != -1:count = count + 1numbers[r][col] = -1

在代码中,我们从网格中所有可能的单元格中随机选择一个数字。我们一直这样做,直到得到所述的地雷数量。

注意:地雷的实际值存储为-1,而为显示而存储的值则表示地雷为 “M”。

注意:"randint "函数只能在导入随机库后使用。在程序开始时写入 "import random "即可。

设置网格编号

对于网格中的每个单元格,我们必须检查所有相邻单元格是否存在地雷。具体方法如下

# Function for setting up the other grid values
def set_values():global numbersglobal n# Loop for counting each cell valuefor r in range(n):for col in range(n):# Skip, if it contains a mineif numbers[r][col] == -1:continue# Check up  if r > 0 and numbers[r-1][col] == -1:numbers[r][col] = numbers[r][col] + 1# Check down    if r < n-1  and numbers[r+1][col] == -1:numbers[r][col] = numbers[r][col] + 1# Check leftif col > 0 and numbers[r][col-1] == -1:numbers[r][c] = numbers[r][c] + 1# Check rightif col < n-1 and numbers[r][col+1] == -1:numbers[r][col] = numbers[r][col] + 1# Check top-left    if r > 0 and col > 0 and numbers[r-1][col-1] == -1:numbers[r][col] = numbers[r][col] + 1# Check top-rightif r > 0 and col < n-1 and numbers[r-1][col+1]== -1:numbers[r][col] = numbers[r][col] + 1# Check below-left  if r < n-1 and col > 0 and numbers[r+1][col-1]== -1:numbers[r][col] = numbers[r][col] + 1# Check below-rightif r < n-1 and col< n-1 and numbers[r+1][col+1]==-1:numbers[r][col] = numbers[r][col] + 1

这些值是不对玩家公开的,因此被存储在数字变量中。

游戏循环

游戏循环是游戏中非常关键的一部分。它需要更新玩家的每一步棋以及游戏的结局。

# Set the mines
set_mines()# Set the values
set_values()# Display the instructions
instructions()# Variable for maintaining Game Loop
over = False# The GAME LOOP 
while not over:print_mines_layout()

在循环的每次迭代中,都必须显示扫雷网格并处理玩家的移动。

处理玩家输入

正如我们之前提到的,有两种玩家输入:

# Input from the user
inp = input("Enter row number followed by space and column number = ").split()

标准输入

在普通移动中,会提到行和列的编号。玩家此举的动机是解锁一个没有地雷的单元格。

# Standard Move
if len(inp) == 2:# Try block to handle errant inputtry: val = list(map(int, inp))except ValueError:clear()print("Wrong input!")instructions()continue

旗子输入

在插旗动作中,游戏者会输入三个数值。前两个值表示小区位置,最后一个值表示插旗。

# Flag Input
elif len(inp) == 3:if inp[2] != 'F' and inp[2] != 'f':clear()print("Wrong Input!")instructions()continue# Try block to handle errant input  try:val = list(map(int, inp[:2]))except ValueError:clear()print("Wrong input!")instructions()continue

净化输入

在存储输入后,我们必须进行一些合理性检查,以便游戏顺利运行。

# Sanity checks
if val[0] > n or val[0] < 1 or val[1] > n or val[1] < 1:clear()print("Wrong Input!")instructions()continue# Get row and column numbers
r = val[0]-1
col = val[1]-1

输入过程完成后,行号和列号将被提取并存储在 "r "和 "c "中。

处理标志输入

管理标记输入并不是一个大问题。在标记单元格为地雷之前,需要检查一些先决条件。

必须进行以下检查:

  • 单元格是否已被标记。
  • 要标记的单元是否已经显示给玩家。
  • 标记数量不超过地雷数量。

处理完这些问题后,该单元就会被标记为地雷。

# If cell already been flagged
if [r, col] in flags:clear()print("Flag already set")continue# If cell already been displayed
if mine_values[r][col] != ' ':clear()print("Value already known")continue# Check the number for flags    
if len(flags) < mines_no:clear()print("Flag set")# Adding flag to the listflags.append([r, col])# Set the flag for displaymine_values[r][col] = 'F'continue
else:clear()print("Flags finished")continue    

处理标准输入

标准输入涉及游戏的整体运行。有三种不同的情况:

锚定地雷

一旦玩家选择了有地雷的单元格,游戏就结束了。这可能是运气不好或判断失误造成的。

# If landing on a mine --- GAME OVER    
if numbers[r][col] == -1:mine_values[r][col] = 'M'show_mines()print_mines_layout()print("Landed on a mine. GAME OVER!!!!!")over = Truecontinue

当我们降落到有地雷的单元格后,我们需要显示游戏中的所有地雷,并改变游戏循环后面的变量。

函数 "show_mines() "负责执行此操作。

def show_mines():global mine_valuesglobal numbersglobal nfor r in range(n):for col in range(n):if numbers[r][col] == -1:mine_values[r][col] = 'M'

访问 "0 "值单元格。
创建游戏最棘手的部分就是管理这种情况。每当游戏者访问一个 "0 "值单元格时,所有相邻的元素都必须显示出来,直到访问到一个非零值单元格为止。

# If landing on a cell with 0 mines in neighboring cells
elif numbers[r][n] == 0:vis = []mine_values[r][n] = '0'neighbours(r, col)  

这一目标可以通过递归来实现。递归是一种编程工具,其中的函数会调用自身,直到基本情况得到满足。相邻函数就是一个递归函数,它解决了我们的问题。

def neighbours(r, col):global mine_valuesglobal numbersglobal vis# If the cell already not visitedif [r,col] not in vis:# Mark the cell visitedvis.append([r,col])# If the cell is zero-valuedif numbers[r][col] == 0:# Display it to the usermine_values[r][col] = numbers[r][col]# Recursive calls for the neighbouring cellsif r > 0:neighbours(r-1, col)if r < n-1:neighbours(r+1, col)if col > 0:neighbours(r, col-1)if col < n-1:neighbours(r, col+1)    if r > 0 and col > 0:neighbours(r-1, col-1)if r > 0 and col < n-1:neighbours(r-1, col+1)if r < n-1 and col > 0:neighbours(r+1, col-1)if r < n-1 and col < n-1:neighbours(r+1, col+1)  # If the cell is not zero-valued            if numbers[r][col] != 0:mine_values[r][col] = numbers[r][col]

针对游戏的这一特殊概念,我们使用了一种新的数据结构,即 vis。vis 的作用是在递归过程中跟踪已访问过的单元格。如果没有这些信息,递归将永远持续下去。

在显示所有零值单元格及其相邻单元格后,我们就可以进入最后一个场景了。

选择非零值单元格

处理这种情况无需费力,因为我们只需更改显示值即可。

# If selecting a cell with atleast 1 mine in neighboring cells  
else:   mine_values[r][col] = numbers[r][col]

结束游戏

每次下棋时,都需要检查棋局是否结束。具体做法如下

# Check for game completion 
if(check_over()):show_mines()print_mines_layout()print("Congratulations!!! YOU WIN")over = Truecontinue

函数 check_over()负责检查游戏是否结束。

# Function to check for completion of the game
def check_over():global mine_valuesglobal nglobal mines_no# Count of all numbered valuescount = 0# Loop for checking each cell in the gridfor r in range(n):for col in range(n):# If cell not empty or flaggedif mine_values[r][col] != ' ' and mine_values[r][col] != 'F':count = count + 1# Count comparison          if count == n * n - mines_no:return Trueelse:return False

我们计算没有空格或标记的单元格数量。当这一数字等于除含有地雷的单元格外的所有单元格时,游戏即宣告结束。

每次移动后清除输出

当我们不断在终端上打印内容时,终端就会变得很拥挤。因此,必须不断清除输出。方法如下

# Function for clearing the terminal
def clear():os.system("clear")

完整代码

以下是扫雷游戏的完整代码:

# Importing packages
import random
import os# Printing the Minesweeper Layout
def print_mines_layout():global mine_valuesglobal nprint()print("\t\t\tMINESWEEPER\n")st = "   "for i in range(n):st = st + "     " + str(i + 1)print(st)   for r in range(n):st = "     "if r == 0:for col in range(n):st = st + "______" print(st)st = "     "for col in range(n):st = st + "|     "print(st + "|")st = "  " + str(r + 1) + "  "for col in range(n):st = st + "|  " + str(mine_values[r][col]) + "  "print(st + "|") st = "     "for col in range(n):st = st + "|_____"print(st + '|')print()# Function for setting up Mines
def set_mines():global numbersglobal mines_noglobal n# Track of number of mines already set upcount = 0while count < mines_no:# Random number from all possible grid positions val = random.randint(0, n*n-1)# Generating row and column from the numberr = val // ncol = val % n# Place the mine, if it doesn't already have oneif numbers[r][col] != -1:count = count + 1numbers[r][col] = -1# Function for setting up the other grid values
def set_values():global numbersglobal n# Loop for counting each cell valuefor r in range(n):for col in range(n):# Skip, if it contains a mineif numbers[r][col] == -1:continue# Check up  if r > 0 and numbers[r-1][col] == -1:numbers[r][col] = numbers[r][col] + 1# Check down    if r < n-1  and numbers[r+1][col] == -1:numbers[r][col] = numbers[r][col] + 1# Check leftif col > 0 and numbers[r][col-1] == -1:numbers[r][col] = numbers[r][col] + 1# Check rightif col < n-1 and numbers[r][col+1] == -1:numbers[r][col] = numbers[r][col] + 1# Check top-left    if r > 0 and col > 0 and numbers[r-1][col-1] == -1:numbers[r][col] = numbers[r][col] + 1# Check top-rightif r > 0 and col < n-1 and numbers[r-1][col+1] == -1:numbers[r][col] = numbers[r][col] + 1# Check below-left  if r < n-1 and col > 0 and numbers[r+1][col-1] == -1:numbers[r][col] = numbers[r][col] + 1# Check below-rightif r < n-1 and col < n-1 and numbers[r+1][col+1] == -1:numbers[r][col] = numbers[r][col] + 1# Recursive function to display all zero-valued neighbours  
def neighbours(r, col):global mine_valuesglobal numbersglobal vis# If the cell already not visitedif [r,col] not in vis:# Mark the cell visitedvis.append([r,col])# If the cell is zero-valuedif numbers[r][col] == 0:# Display it to the usermine_values[r][col] = numbers[r][col]# Recursive calls for the neighbouring cellsif r > 0:neighbours(r-1, col)if r < n-1:neighbours(r+1, col)if col > 0:neighbours(r, col-1)if col < n-1:neighbours(r, col+1)    if r > 0 and col > 0:neighbours(r-1, col-1)if r > 0 and col < n-1:neighbours(r-1, col+1)if r < n-1 and col > 0:neighbours(r+1, col-1)if r < n-1 and col < n-1:neighbours(r+1, col+1)  # If the cell is not zero-valued            if numbers[r][col] != 0:mine_values[r][col] = numbers[r][col]# Function for clearing the terminal
def clear():os.system("clear")      # Function to display the instructions
def instructions():print("Instructions:")print("1. Enter row and column number to select a cell, Example \"2 3\"")print("2. In order to flag a mine, enter F after row and column numbers, Example \"2 3 F\"")# Function to check for completion of the game
def check_over():global mine_valuesglobal nglobal mines_no# Count of all numbered valuescount = 0# Loop for checking each cell in the gridfor r in range(n):for col in range(n):# If cell not empty or flaggedif mine_values[r][col] != ' ' and mine_values[r][col] != 'F':count = count + 1# Count comparison          if count == n * n - mines_no:return Trueelse:return False# Display all the mine locations                    
def show_mines():global mine_valuesglobal numbersglobal nfor r in range(n):for col in range(n):if numbers[r][col] == -1:mine_values[r][col] = 'M'if __name__ == "__main__":# Size of gridn = 8# Number of minesmines_no = 8# The actual values of the gridnumbers = [[0 for y in range(n)] for x in range(n)] # The apparent values of the gridmine_values = [[' ' for y in range(n)] for x in range(n)]# The positions that have been flaggedflags = []# Set the minesset_mines()# Set the valuesset_values()# Display the instructionsinstructions()# Variable for maintaining Game Loopover = False# The GAME LOOP while not over:print_mines_layout()# Input from the userinp = input("Enter row number followed by space and column number = ").split()# Standard inputif len(inp) == 2:# Try block to handle errant inputtry: val = list(map(int, inp))except ValueError:clear()print("Wrong input!")instructions()continue# Flag inputelif len(inp) == 3:if inp[2] != 'F' and inp[2] != 'f':clear()print("Wrong Input!")instructions()continue# Try block to handle errant input  try:val = list(map(int, inp[:2]))except ValueError:clear()print("Wrong input!")instructions()continue# Sanity checks if val[0] > n or val[0] < 1 or val[1] > n or val[1] < 1:clear()print("Wrong input!")instructions()continue# Get row and column numbersr = val[0]-1col = val[1]-1 # If cell already been flaggedif [r, col] in flags:clear()print("Flag already set")continue# If cell already been displayedif mine_values[r][col] != ' ':clear()print("Value already known")continue# Check the number for flags    if len(flags) < mines_no:clear()print("Flag set")# Adding flag to the listflags.append([r, col])# Set the flag for displaymine_values[r][col] = 'F'continueelse:clear()print("Flags finished")continue    else: clear()print("Wrong input!")   instructions()continue# Sanity checksif val[0] > n or val[0] < 1 or val[1] > n or val[1] < 1:clear()print("Wrong Input!")instructions()continue# Get row and column numberr = val[0]-1col = val[1]-1# Unflag the cell if already flaggedif [r, col] in flags:flags.remove([r, col])# If landing on a mine --- GAME OVER    if numbers[r][col] == -1:mine_values[r][col] = 'M'show_mines()print_mines_layout()print("Landed on a mine. GAME OVER!!!!!")over = Truecontinue# If landing on a cell with 0 mines in neighboring cellselif numbers[r][col] == 0:vis = []mine_values[r][col] = '0'neighbours(r, col)# If selecting a cell with atleast 1 mine in neighboring cells  else:   mine_values[r][col] = numbers[r][col]# Check for game completion if(check_over()):show_mines()print_mines_layout()print("Congratulations!!! YOU WIN")over = Truecontinueclear() 

结论

我们希望本教程能让大家明白如何创建自己的扫雷游戏,并从中获得乐趣。如有任何疑问,欢迎在下方评论。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com