Skip to content

第5章 开发案例:纸牌配对游戏

纸牌配对游戏是一款经典且富有挑战性的记忆游戏,适合所有年龄段玩家。游戏的核心玩法在于通过记忆和推理,将翻开的纸牌与其背面图案相同的另一张纸牌配对成功。在纸牌配对游戏中,玩家需要翻开背面向上的纸牌,记住每张牌的图案,然后寻找与其图案相同的另一张牌进行配对。游戏开始时,所有纸牌均背面向上放置,玩家每次可以翻开任意一张牌。如果翻开的牌与之前已翻开的某张牌图案相同,则成功配对,两张牌将被移除;若不相同,则玩家需要继续翻开其他牌进行寻找。随着游戏的进行,纸牌数量逐渐减少,玩家需要快速记住已翻开的牌,并精准配对剩余的牌。游戏的挑战性在于不断提升的记忆难度和时间压力,考验玩家的观察力和记忆力。

为了实现简易的纸牌配对游戏,我们与 ChatGPT(GPT-3.5)进行了多轮交互,交互过程主要分为总体思路、游戏页面设计、游戏逻辑设计三个部分。

5.1 总体思路

用户:

请使用Android studio Java语言写一个两两配对的游戏项目,规则是在4x4的方格内依次翻牌,翻到同样的两张牌就将这两张消除,直至所有牌都消除,游戏结束。要求:1. 首先展示16张牌的牌面,然后背面朝上。2. 每一局比赛,16张牌的牌图案应当从备选图案中随机抽取,且保证每种图案的牌都应当为偶数,保证游戏正常结束。请问能指导我怎么一步一步去具体实现吗?


ChatGPT:

当然可以!首先,我们需要按照您的要求,一步步实现这个游戏项目。我将指导您完成整个过程:

第一步:创建新项目

  1. 打开 Android Studio。
  2. 选择“Create New Project”。
  3. 选择“Empty Activity”模板,并点击“Next”。
  4. 命名您的应用,并选择保存位置,然后点击“Finish”。

第二步:设计游戏界面

  1. 打开 activity_main.xml 文件。
  2. 使用 GridLayout 创建一个 4x4 的网格布局。
  3. 在网格布局中添加 16 个 ImageView 元素,用于显示牌的图案。
  4. 添加一些背景资源作为牌的背面图案。

第三步:准备牌面图案

  1. res 目录下创建一个名为 drawable 的文件夹。
  2. 将您想要的牌面图案添加到 drawable 文件夹中。确保每种图案都有至少两张相同的牌。

第四步:编写 Java 代码

  1. 创建一个 Card 类,表示每张牌,其中应包含图案和是否已被翻开等信息。
  2. 创建一个 Game 类,处理游戏逻辑,如初始化牌堆、随机选择牌面等。
  3. MainActivity.java 中,初始化游戏并将牌面显示在界面上,以及处理点击事件和游戏逻辑。

第五步:实现游戏逻辑

  1. Game 类中,编写方法来初始化牌堆、随机选择牌面等。
  2. 实现点击事件处理逻辑,在用户点击牌时翻开牌并检查是否匹配。
  3. 当所有牌都匹配成功时,显示游戏结束界面。

第六步:测试和调试

  1. 运行应用程序,并测试游戏是否按预期运行。
  2. 检查是否有任何 bug 并进行修复。

第七步:优化和美化

  1. 对界面进行优化,使其更加美观。
  2. 添加动画效果,如翻牌时的过渡效果。

以上是一个简单的步骤指导。在实际编码过程中,您可能需要查阅 Android 开发文档以及相关的 Java 编程资源,特别是关于界面设计和事件处理的部分。如果您在具体实现过程中遇到了问题,随时可以向我提问。祝您编程愉快!


操作: 在Android studio中新建项目。


可以看出来,通过这么一轮的问答,大模型基本给出了整体实现的思路和步骤。然后我们要做的事情是基于这个框架做进一步细化。 注意这里面有一个小技巧,我们前面介绍大模型使用技巧的时候说过,要善于将大的工程项目进行分割,善于将问题进行分割,我们这里就直接利用大模型来帮我们完成分割的事情。

5.2 游戏页面设计

5.2.1 页面基本框架

交互1:游戏页面设计

用户:

好的,首先请帮我设计游戏页面。


ChatGPT:

当设计游戏页面时,我们需要考虑以下几个方面:

  1. 网格布局:一个 4x4 的网格,用来展示牌的图案。
  2. 牌的样式:每张牌有两个状态,一个是背面朝上,一个是图案朝上。
  3. 牌面图案:需要选择一些不同的图案作为牌的正面。
  4. 交互元素:可能需要一些按钮或标签来显示游戏状态、计分等信息。

下面是一个简单的游戏页面设计示例:

<!-- activity_main.xml -->  
<GridLayout  
    android:id="@+id/gridLayout"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:columnCount="4"  
    android:rowCount="4"  
    android:padding="16dp"  
    android:layout_margin="16dp"  
    android:background="@color/white">  

    <!-- 这里添加16个ImageView元素,用于显示牌的图案 -->  

</GridLayout>  

在这个布局中,我们使用了一个 GridLayout,并指定了 4 列和 4 行。每个格子都将放置一个 ImageView 元素,用于显示牌的图案。

接下来,我们需要设计每张牌的布局。通常情况下,一张牌有两个状态:正面和背面。我们可以通过两个不同的图片来表示这两种状态。我们还需要为每个 ImageView 元素设置一个点击事件,以便在用户点击时翻转牌,并检查是否匹配。

让我们开始设计每张牌的布局和样式。我将创建两个 XML 文件,一个用于表示牌的正面,一个用于表示牌的背面:

1.card_front.xml:

<!-- card_front.xml -->  
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:src="@drawable/card_front_image"  
    android:scaleType="fitCenter"  
    android:padding="8dp"  
    android:background="@drawable/card_bg"  
    android:onClick="onCardClicked"  
    android:tag="front"/> 

2.card_back.xml:

<!-- card_back.xml -->  
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:src="@drawable/card_back_image"  
    android:scaleType="fitCenter"  
    android:padding="8dp"  
    android:background="@drawable/card_bg"  
    android:onClick="onCardClicked"  
    android:tag="back"/>  

在这两个 XML 文件中,我们设置了 ImageView 的属性,包括宽度、高度、图像来源、填充和点击事件。card_front.xmlcard_back.xml 都包含一个背景资源 card_bg,用于装饰牌的外观。

现在,您可以在 GridLayout 中添加这些 ImageView 元素,并根据需要设置它们的状态。在实现 Java 代码时,我们将根据用户的点击来切换正面和背面状态。


操作:

将上述代码按照顺序复制粘贴进相应文件中,观察布局内容。我们发现activity_main.xml这个文件并没有完整,因此下一步是需要将这个文件补全。这里我们选择了最直接粗暴的一种方式看看效果。


交互2:页面内容补全

用户:

请帮我补全activity_main.xml


ChatGPT:

当你创建一个简单的 GridLayout 之后,下一步是在每个格子里添加 ImageView 元素,用于显示牌的图案。这些 ImageView 元素将根据游戏的状态(正面或背面)显示不同的图案。此外,我们可能还需要一些其他的 UI 元素,如计分板、重置按钮等。

下面是一个可能的 activity_main.xml 文件的示例,包含了一个 GridLayout 和一些其他的 UI 元素:

<!-- activity_main.xml -->  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="@color/background_color"  
    tools:context=".MainActivity">  

    <!-- 游戏标题 -->  
    <TextView  
        android:id="@+id/textViewTitle"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Memory Game"  
        android:textSize="24sp"  
        android:textColor="@android:color/black"  
        android:layout_centerHorizontal="true"  
        android:layout_marginTop="16dp"/>  

    <!-- 计分板 -->  
    <TextView  
        android:id="@+id/textViewScore"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Score: 0"  
        android:textSize="18sp"  
        android:textColor="@android:color/black"  
        android:layout_below="@id/textViewTitle"  
        android:layout_centerHorizontal="true"  
        android:layout_marginTop="8dp"/>  

    <!-- 重置按钮 -->  
    <Button  
        android:id="@+id/buttonReset"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Reset"  
        android:textSize="18sp"  
        android:textColor="@android:color/white"  
        android:background="@drawable/button_bg"  
        android:layout_below="@id/textViewScore"  
        android:layout_centerHorizontal="true"  
        android:layout_marginTop="8dp"/>  

    <!-- 游戏网格 -->  
    <GridLayout  
        android:id="@+id/gridLayout"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:columnCount="4"  
        android:rowCount="4"  
        android:layout_below="@id/buttonReset"  
        android:padding="16dp"  
        android:layout_margin="16dp"  
        android:background="@drawable/grid_bg"/>  

</RelativeLayout>  

在这个示例中,我们添加了一个标题文本、一个计分板、一个重置按钮以及一个 GridLayout 用于展示游戏网格。您可以根据需要修改这些元素的样式和布局。例如,您可以使用不同的颜色、大小和边距来改变元素的外观。


操作:

将代码复制粘贴进activity_main.xml,观察布局内容。这时候我们发现这里面有一个计分板,不是目标要求里面的内容,这就是大模型的幻觉了。 另外特别提一下,我们这里只要大模型补全,并没有额外说其他的要求,大模型就能够提供很详细的代码,这里的主要原因就是大模型有之前问题的上下文,前面我们将需求描述得很清楚,所以这里大模型还能够比较清楚的理解。


交互3:页面无关内容删除

用户:

我不需要计分板


ChatGPT:

如果你不需要计分板,可以简化 activity_main.xml 文件,只保留标题、重置按钮和游戏网格。下面是一个简化版本的 activity_main.xml

<!-- activity_main.xml -->  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="@color/background_color"  
    tools:context=".MainActivity">  

    <!-- 游戏标题 -->  
    <TextView  
        android:id="@+id/textViewTitle"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Memory Game"  
        android:textSize="24sp"  
        android:textColor="@android:color/black"  
        android:layout_centerHorizontal="true"  
        android:layout_marginTop="16dp"/>  

    <!-- 重置按钮 -->  
    <Button  
        android:id="@+id/buttonReset"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Reset"  
        android:textSize="18sp"  
        android:textColor="@android:color/white"  
        android:background="@drawable/button_bg"  
        android:layout_below="@id/textViewTitle"  
        android:layout_centerHorizontal="true"  
        android:layout_marginTop="8dp"/>  

    <!-- 游戏网格 -->  
    <GridLayout  
        android:id="@+id/gridLayout"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:columnCount="4"  
        android:rowCount="4"  
        android:layout_below="@id/buttonReset"  
        android:padding="16dp"  
        android:layout_margin="16dp"  
        android:background="@drawable/grid_bg"/>  

</RelativeLayout>  

在这个版本中,我们去掉了计分板,并将重置按钮放在了标题的下方。您可以根据自己的需求调整布局和样式。


操作:

将代码复制粘贴进activity_main.xml,观察布局内容,下一步是设计4x4 的卡牌布局。


交互4:卡片布局设计

用户:

请帮我在GridLayout下设计 4x4 的卡牌布局


ChatGPT:

当你在 GridLayout 下设计 4x4 的卡牌布局时,你需要在 XML 中添加 16 个 ImageView 元素,每个元素代表一个卡牌。这些 ImageView 元素将用于显示卡牌的图案。以下是一个简单的示例:

<!-- activity_main.xml -->  
<GridLayout  
    android:id="@+id/gridLayout"  
    android:layout_width="match_parent"  
    android:layout_height="wrap_content"  
    android:columnCount="4"  
    android:rowCount="4"  
    android:layout_below="@id/buttonReset"  
    android:padding="16dp"  
    android:layout_margin="16dp"  
    android:background="@drawable/grid_bg">  

    <!-- 第一行 -->  
    <ImageView  
        android:id="@+id/imageView00"  
        android:layout_width="0dp"  
        android:layout_height="wrap_content"  
        android:layout_columnWeight="1"  
        android:layout_rowWeight="1"  
        android:scaleType="fitCenter"  
        android:src="@drawable/card_back_image"  
        android:onClick="onCardClicked"  
        android:tag="back"/>  

    <ImageView  
        android:id="@+id/imageView01"  
        android:layout_width="0dp"  
        android:layout_height="wrap_content"  
        android:layout_columnWeight="1"  
        android:layout_rowWeight="1"  
        android:scaleType="fitCenter"  
        android:src="@drawable/card_back_image"  
        android:onClick="onCardClicked"  
        android:tag="back"/>  

    <!-- 继续添加第一行的 ImageView 元素 -->  

    <!-- 第二行 -->  
    <ImageView  
        android:id="@+id/imageView10"  
        android:layout_width="0dp"  
        android:layout_height="wrap_content"  
        android:layout_columnWeight="1"  
        android:layout_rowWeight="1"  
        android:scaleType="fitCenter"  
        android:src="@drawable/card_back_image"  
        android:onClick="onCardClicked"  
        android:tag="back"/>  

    <!-- 继续添加第二行的 ImageView 元素 -->  

    <!-- 继续添加第三行和第四行的 ImageView 元素 -->  

</GridLayout>

在这个示例中,我们将每个 ImageView 元素放置在了 GridLayout 中,并设置了它们的宽度和高度为 0dp,以便让它们根据列和行的权重进行平均分配。我们使用 layout_columnWeightlayout_rowWeight 来设置每个元素的权重,以使它们在网格中均匀分布。

您需要在这个模板中添加更多的 ImageView 元素,以填满整个 4x4 的卡牌布局。每个元素的 android:src 属性可以设置为 card_back_image,代表卡牌的背面图案。


操作:

1.将代码复制粘贴进activity_main.xml,添加ImageView 元素,以填满整个 4x4 的卡牌布局,简单调整页面布局如下:

<?xml version="1.0" encoding="utf-8"?>  
<!-- activity_main.xml -->  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="#CBFFF6A9"  
    tools:context=".MainActivity">  

    <!-- 游戏标题 -->  
    <TextView  
        android:id="@+id/textViewTitle"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_centerHorizontal="true"  
        android:layout_marginTop="50dp"  
        android:textStyle="bold"  
        android:text="翻牌游戏"  
        android:textSize="24sp" />  


    <!-- 重置按钮 -->  
    <Button  
        android:id="@+id/buttonReset"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="重置"  
        android:textSize="18sp"  
        android:textColor="@android:color/white"  
        android:layout_below="@id/textViewTitle"  
        android:onClick="onRestartClicked"  
        android:layout_centerHorizontal="true"  
        android:layout_marginTop="30dp"/>  

    <!-- 游戏网格 -->  
    <GridLayout  
        android:id="@+id/gridLayout"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:layout_below="@id/buttonReset"  
        android:layout_marginStart="6dp"  
        android:layout_marginTop="60dp"  
        android:layout_marginEnd="6dp"  
        android:layout_marginBottom="80dp"  
        android:columnCount="4"  
        android:rowCount="4">  

        <!-- 第一行 -->  
        <ImageView  
            android:id="@+id/imageView00"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView01"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:layout_marginStart="5dp"  
            android:layout_marginTop="5dp"  
            android:layout_marginEnd="5dp"  
            android:layout_marginBottom="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView02"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView03"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView04"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:layout_marginStart="5dp"  
            android:layout_marginTop="5dp"  
            android:layout_marginEnd="5dp"  
            android:layout_marginBottom="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView05"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView06"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView07"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView08"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView09"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView10"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView11"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView12"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView13"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView14"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  

        <ImageView  
            android:id="@+id/imageView15"  
            android:layout_width="0dp"  
            android:layout_height="0dp"  
            android:layout_rowWeight="1"  
            android:layout_columnWeight="1"  
            android:layout_margin="5dp"  
            android:onClick="onCardClicked"  
            android:scaleType="fitCenter"  
            android:src="@drawable/card_back_image"  
            android:tag="back" />  


    </GridLayout>  

</RelativeLayout>  

2.添加图片资源,分别为8种正面图案card_pattern0-7,以及一种背面图案card_back_image,保存在res/drawable中,如下:

5-1

3.此时得到整体页面框架如下:

5-2

5.2.2 纸牌初始化

交互1:纸牌图案分配

用户:

我已经设计了8种牌面的正面图案,分别为:@drawable/card_pattern0 @drawable/card_pattern1 @drawable/card_pattern2 @drawable/card_pattern3 @drawable/card_pattern4 @drawable/card_pattern5 @drawable/card_pattern6 @drawable/card_pattern7,以及一种背面图案@drawable/card_back_image。


ChatGPT:

好的,那么现在我们需要将这些图案随机分配到卡牌上。为了确保每种图案都有两张卡牌,我们可以先创建一个包含所有图案的列表,并随机排列它,然后将排列后的列表中的前八个图案分别复制一次,得到一共 16 张卡牌的正面图案。

下面是一个简单的方法来实现这个逻辑:

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  

public class Game {  

    private List<Integer> cardPatterns;  

    public Game() {  
        // 初始化牌面图案列表  
        cardPatterns = new ArrayList<>();  
        for (int i = 0; i < 8; i++) {  
            cardPatterns.add(i);  
            cardPatterns.add(i); // 每种图案复制一次,保证每种图案有两张卡牌  
        }  
        // 打乱列表顺序  
        Collections.shuffle(cardPatterns);  
    }  
      public List<Integer> getCardPatterns() {  
        return cardPatterns;  
    }  
}  

这段代码创建了一个 Game 类,其中包含一个名为 cardPatterns 的列表,用于存储牌面图案。在构造函数中,我们将 0 到 7 的数字添加到列表中,代表了八种不同的图案。然后,我们将列表打乱顺序,以确保每种图案的牌都被随机分配到不同的位置。最后,我们提供了一个 getCardPatterns() 方法,用于获取随机排列后的图案列表。

现在,我们可以在 MainActivity.java 中使用 Game 类来获取随机排列的牌面图案,并将它们分配给卡牌的正面图案。我们可以在 onCreate() 方法中完成这个操作。具体的实现逻辑如下:

import android.os.Bundle;  
import android.widget.ImageView;  
import androidx.appcompat.app.AppCompatActivity;  
import java.util.List;  

public class MainActivity extends AppCompatActivity {  

    private Game game;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        // 创建游戏实例  
        game = new Game();  
        // 获取卡牌正面图案列表  
        List<Integer> cardPatterns = game.getCardPatterns();  
        // 获取GridLayout  
        GridLayout gridLayout = findViewById(R.id.gridLayout);  
        // 获取GridLayout中的ImageView元素并设置正面图案  
        for (int i = 0; i < gridLayout.getChildCount(); i++) {  
            ImageView imageView = (ImageView) gridLayout.getChildAt(i);  
            imageView.setImageResource(cardPatterns.get(i));  
        }  
    }  
}  

在这段代码中,我们在 onCreate() 方法中创建了一个 Game 对象,然后调用 getCardPatterns() 方法获取了随机排列的牌面图案列表。接下来,我们获取了 GridLayout 中的 ImageView 元素,并通过索引从牌面图案列表中获取对应的图案,并将其设置为 ImageView 的正面图案。

现在,当您运行应用程序时,您将在卡牌的正面看到随机排列的图案。


操作: 1. 新建Game.java文件,将上述内容中第一段代码复制粘贴进Game.java 2. 将第二段代码复制粘贴进MainActivity.java 这时候意外出现了,我们运行代码时发现报错,这时候不要慌张,我们说过这样的问题是非常正常的,将问题重新输入给ChatGPT。


交互2:编辑错误解决

用户:

Cannot resolve symbol 'GridLayout'


ChatGPT:

如果您在代码中遇到了 Cannot resolve symbol 'GridLayout' 的错误,这通常意味着您的代码中缺少了必要的引入或导入语句。在 Android 中,GridLayout 是 Android SDK 中的一个类,它位于 android.widget 包中。

您需要在您的 Java 文件中导入 GridLayout 类。请尝试在您的 MainActivity.java 文件中添加以下导入语句:

import android.widget.GridLayout;  

确保这行代码位于文件的开头,位于其他导入语句之前。导入语句告诉编译器您在代码中使用了哪些类或包,并帮助编译器正确地解析类名。

添加了导入语句后,编译器应该能够解析 GridLayout 类,并消除 Cannot resolve symbol 'GridLayout' 错误。

操作:

添加import语句,但是还是运行失败,这时候我们需要自己去查看一下代码了,我们发现主要问题可能源于2.2.1交互过程中“在构造函数中,我们将 0 到 7 的数字添加到列表中,代表了八种不同的图案。”但是实际上我们的图片并不是这样命名的。

交互3:图案名称修改

用户:

我们用数字 0 到 7 ,代表了八种不同的图案,但是图案的真实名称是@drawable/card_pattern0 @drawable/card_pattern1 @drawable/card_pattern2 @drawable/card_pattern3 @drawable/card_pattern4 @drawable/card_pattern5 @drawable/card_pattern6 @drawable/card_pattern7,setImageResource时应当是真实名称。


ChatGPT:

明白了,您想在 setImageResource() 方法中使用真实的图案名称而不是数字。那么我们需要将数字映射到真实的图案名称。您可以使用一个数组或者集合来完成这个映射。

这里有一个使用数组的示例:

import android.os.Bundle;  
import android.widget.ImageView;  
import androidx.appcompat.app.AppCompatActivity;  
import java.util.List;  

public class MainActivity extends AppCompatActivity {  
    private Game game;  
    // 图案资源数组  
    private int[] patternResources = {  
        R.drawable.card_pattern0,  
        R.drawable.card_pattern1,  
        R.drawable.card_pattern2,  
        R.drawable.card_pattern3,  
        R.drawable.card_pattern4,  
        R.drawable.card_pattern5,  
        R.drawable.card_pattern6,  
        R.drawable.card_pattern7  
    };  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        // 创建游戏实例  
        game = new Game();  
         // 获取卡牌正面图案列表  
        List<Integer> cardPatterns = game.getCardPatterns();  
          // 获取GridLayout  
        GridLayout gridLayout = findViewById(R.id.gridLayout);  
          // 获取GridLayout中的ImageView元素并设置正面图案  
        for (int i = 0; i < gridLayout.getChildCount(); i++) {  
            ImageView imageView = (ImageView) gridLayout.getChildAt(i);  
            int patternIndex = cardPatterns.get(i);  
            imageView.setImageResource(patternResources[patternIndex]);  
        }  
    }  
}  

在这个示例中,我们创建了一个名为 patternResources 的数组,其中包含了图案的真实名称。在 onCreate() 方法中,我们根据随机排列的图案列表中的数字索引,从 patternResources 数组中获取对应的真实图案名称,并将其设置为 ImageView 的正面图案。

这样,您的卡牌正面图案就会被正确地设置为真实的图案名称了。


操作:

将上述代码以及import android.widget.GridLayout;重新全部复制粘贴进MainActivity.java文件内,运行后可以得到如下页面,可以看出每张图都被赋予了正面图案,并且保证了每种图片都有两张卡牌:

5-3

5.3 游戏逻辑设计

5.3.1数据结构设计

交互1:卡牌背面展示

用户:

好的,图案设计部分已完成,现在开始是游戏逻辑设计。首先,在游戏开始时应当展示16张牌的背面,你只需改动这一步即可


ChatGPT:

好的,让我们来实现游戏开始时展示 16 张牌的背面的逻辑。在 MainActivity.javaonCreate() 方法中,我们可以简单地遍历游戏网格中的每个 ImageView 元素,并将其设置为背面图案。

下面是相应的代码修改:

import android.os.Bundle;  
import android.widget.ImageView;  
import androidx.appcompat.app.AppCompatActivity;  
import java.util.List;  

public class MainActivity extends AppCompatActivity {  

    private Game game;  

    private int[] patternResources = {  
        R.drawable.card_pattern0,  
        R.drawable.card_pattern1,  
        R.drawable.card_pattern2,  
        R.drawable.card_pattern3,  
        R.drawable.card_pattern4,  
        R.drawable.card_pattern5,  
        R.drawable.card_pattern6,  
        R.drawable.card_pattern7  
    };  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        // 创建游戏实例  
        game = new Game();  

        // 获取GridLayout  
        GridLayout gridLayout = findViewById(R.id.gridLayout);  

        // 获取GridLayout中的ImageView元素并设置背面图案  
        for (int i = 0; i < gridLayout.getChildCount(); i++) {  
            ImageView imageView = (ImageView) gridLayout.getChildAt(i);  
            imageView.setImageResource(R.drawable.card_back_image); // 设置为背面图案  
        }  
    }  
}  

在这个修改后的代码中,我们遍历了游戏网格中的每个 ImageView 元素,并将其设置为背面图案 R.drawable.card_back_image。这样,在游戏开始时,所有的牌都将展示背面图案。


操作:

重新全部复制粘贴进MainActivity.java文件内,实现所有的牌在一开始展示背面图案: 5-4 后面的操作如果我们有相关编程经验的话,我们可以将问题问得更加具体。


交互2:卡牌状态记录

用户:

之后,我们创建一个Card类,记录每张牌的id以及是否已被翻开


ChatGPT: 好的,让我们创建一个 Card 类来记录每张牌的信息。这个类将包含两个字段:id 用于标识每张牌,isFlipped 用于表示牌是否已被翻开。

以下是 Card 类的简单实现:

public class Card {  
    private int id;  
    private boolean isFlipped;  

    public Card(int id) {  
        this.id = id;  
        this.isFlipped = false; // 默认情况下,牌是未翻开的  
    }  

    public int getId() {  
        return id;  
    }  

    public boolean isFlipped() {  
        return isFlipped;  
    }  

    public void flip() {  
        isFlipped = !isFlipped; // 翻转牌的状态  
    }  
}  

在这个 Card 类中,我们有一个 id 字段用于标识每张牌,以及一个 isFlipped 字段用于表示牌是否已被翻开。我们还有一个构造函数,用于初始化牌的 id,并将 isFlipped 设置为 false 表示牌是未翻开的状态。flip() 方法用于翻转牌的状态,即将 isFlipped 的值取反。

现在我们可以在游戏中使用 Card 类来表示每张牌,并记录其翻转状态了。


操作: 新建Card.java文件,并且将代码全部复制粘贴进Card.java中


5.3.2 交互事件处理

交互1:实现点击事件

用户:

现在我们实现点击事件处理,在这一步我们只需将牌面从背面翻至正面,若牌本身是正面则不处理,后续检查工作之后指示


ChatGPT:

好的,让我们在 MainActivity.java 中处理点击事件,并实现将牌面从背面翻至正面的逻辑。我们将在 ImageView 的点击事件处理方法 onCardClicked() 中完成这个任务。 首先,我们需要在 activity_main.xml 中的每个 ImageView 元素中添加 android:onClick 属性,以指定点击事件的处理方法。然后,在 MainActivity.java 中实现 onCardClicked() 方法来处理点击事件。

以下是相应的代码修改:

1.activity_main.xml:

<ImageView  
    android:id="@+id/imageView00"  
    android:layout_width="0dp"  
    android:layout_height="wrap_content"  
    android:layout_columnWeight="1"  
    android:layout_rowWeight="1"  
    android:scaleType="fitCenter"  
    android:src="@drawable/card_back_image"  
    android:onClick="onCardClicked"  
    android:tag="back"/>  

2.MainActivity.java:

import android.os.Bundle;  
import android.view.View;  
import android.widget.ImageView;  
import androidx.appcompat.app.AppCompatActivity;  
import java.util.List;  

public class MainActivity extends AppCompatActivity {  

    private Game game;  
    private List<Card> cards;  

    private int[] patternResources = {  
        R.drawable.card_pattern0,  
        R.drawable.card_pattern1,  
        R.drawable.card_pattern2,  
        R.drawable.card_pattern3,  
        R.drawable.card_pattern4,  
        R.drawable.card_pattern5,  
        R.drawable.card_pattern6,  
        R.drawable.card_pattern7  
    };  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        // 创建游戏实例  
        game = new Game();  
        cards = game.getCards();  

        // 获取GridLayout  
        GridLayout gridLayout = findViewById(R.id.gridLayout);  

        // 获取GridLayout中的ImageView元素并设置背面图案  
        for (int i = 0; i < gridLayout.getChildCount(); i++) {  
            ImageView imageView = (ImageView) gridLayout.getChildAt(i);  
            imageView.setImageResource(R.drawable.card_back_image); // 设置为背面图案  
            imageView.setTag(i); // 将索引作为标记存储在ImageView中  
        }  
    }  

    public void onCardClicked(View view) {  
        int index = (int) view.getTag(); // 获取被点击的ImageView的索引  
        Card card = cards.get(index); // 获取对应的卡牌对象  

        if (!card.isFlipped()) { // 如果牌是未翻开的  
            // 将牌翻至正面  
            ImageView imageView = (ImageView) view;  
            imageView.setImageResource(patternResources[card.getId()]);  
            card.flip(); // 更新牌的状态为已翻开  
        }  
    }  
}  

在这个修改后的代码中,我们通过 android:onClick="onCardClicked"onCardClicked() 方法与每个 ImageView 元素的点击事件关联起来。当用户点击任何一个卡牌时,都会调用 onCardClicked() 方法。

onCardClicked() 方法中,我们首先从被点击的 ImageView 中获取索引,然后根据索引从 cards 列表中获取对应的 Card 对象。接着,我们检查该牌是否已被翻开,如果未翻开,则将其翻至正面,并更新 Card 对象的状态为已翻开。

现在,点击任何一个卡牌时,都会将其从背面翻至正面。


操作:

在相应文件中修改对应的代码,activity_main.xml中每个ImageView都需要进行修改,保证有onclick函数的存在。

// 创建游戏实例  
        game = new Game();  
        cards = game.getCards();  

但是上述代码getCards()标红出错,因为game类并没有这个函数


交互2:获取卡牌函数补全

用户: game没有getCards()函数


ChatGPT:

如果 Game 类没有提供 getCards() 方法来获取牌的列表,我们需要对 Game 类进行一些修改,以便在 MainActivity.java 中能够获取到牌的列表。

以下是修改后的 Game 类:

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  

public class Game {  

    private List<Card> cards;  

    public Game() {  
        // 初始化牌堆  
        cards = new ArrayList<>();  
        for (int i = 0; i < 8; i++) {  
            cards.add(new Card(i)); // 每种图案添加一张牌  
            cards.add(new Card(i)); // 每种图案添加另一张牌  
        }  
        // 打乱牌堆顺序  
        Collections.shuffle(cards);  
    }  

    public List<Card> getCards() {  
        return cards;  
    }  
}  

在这个修改后的 Game 类中,我们添加了一个 getCards() 方法,用于获取牌的列表。在构造函数中,我们初始化了一个包含 16 张牌的列表,并为每种图案添加了两张牌。然后,我们使用 Collections.shuffle() 方法对列表进行了随机排序。

现在,在 MainActivity.java 中,我们可以使用 getCards() 方法来获取牌的列表,并根据索引从列表中获取对应的 Card 对象。接下来,让我们更新 MainActivity.java 中的代码,以便使用新的 Game 类。

import android.os.Bundle;  
import android.view.View;  
import android.widget.ImageView;  
import androidx.appcompat.app.AppCompatActivity;  
import java.util.List;  

public class MainActivity extends AppCompatActivity {  

    private Game game;  
    private List<Card> cards;  

    private int[] patternResources = {  
        R.drawable.card_pattern0,  
        R.drawable.card_pattern1,  
        R.drawable.card_pattern2,  
        R.drawable.card_pattern3,  
        R.drawable.card_pattern4,  
        R.drawable.card_pattern5,  
        R.drawable.card_pattern6,  
        R.drawable.card_pattern7  
    };  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        // 创建游戏实例  
        game = new Game();  
        cards = game.getCards();  

        // 获取GridLayout  
        GridLayout gridLayout = findViewById(R.id.gridLayout);  

        // 获取GridLayout中的ImageView元素并设置背面图案  
        for (int i = 0; i < gridLayout.getChildCount(); i++) {  
            ImageView imageView = (ImageView) gridLayout.getChildAt(i);  
            imageView.setImageResource(R.drawable.card_back_image); // 设置为背面图案  
            imageView.setTag(i); // 将索引作为标记存储在ImageView中  
        }  
    }  

    public void onCardClicked(View view) {  
        int index = (int) view.getTag(); // 获取被点击的ImageView的索引  
        Card card = cards.get(index); // 获取对应的卡牌对象  

        if (!card.isFlipped()) { // 如果牌是未翻开的  
            // 将牌翻至正面  
            ImageView imageView = (ImageView) view;  
            imageView.setImageResource(patternResources[card.getId()]);  
            card.flip(); // 更新牌的状态为已翻开  
        }  
    }  
}  

现在,您应该能够在 MainActivity.java 中使用 getCards() 方法来获取牌的列表了。


操作:

在相应文件中修改对应的代码,此时点击处于背面的卡牌,会出现正面的图片,即完成卡牌的翻转,如下图所示,点击第一张卡牌,会从背面翻转成正面(默认问号图片为背面,其他图片为正面): 5-5

5-6

交互3:函数结构全局适配

用户:

相应的,请修改MainActivity文件


ChatGPT:

好的,让我们修改 MainActivity.java 文件以适应新的 Game 类结构,并在点击事件处理方法中使用 Game 类中的 Card 对象。

import android.os.Bundle;  
import android.view.View;  
import android.widget.ImageView;  
import androidx.appcompat.app.AppCompatActivity;  

public class MainActivity extends AppCompatActivity {  

    private Game game;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        // 创建游戏实例  
        game = new Game();  

        // 获取GridLayout  
        GridLayout gridLayout = findViewById(R.id.gridLayout);  

        // 获取GridLayout中的ImageView元素并设置背面图案  
        for (int i = 0; i < gridLayout.getChildCount(); i++) {  
            ImageView imageView = (ImageView) gridLayout.getChildAt(i);  
            imageView.setImageResource(R.drawable.card_back_image); // 设置为背面图案  
            imageView.setTag(i); // 将索引作为标记存储在ImageView中  
        }  
    }  

    public void onCardClicked(View view) {  
        int index = (int) view.getTag(); // 获取被点击的ImageView的索引  
        Card card = game.getCardAtIndex(index); // 获取对应的卡牌对象  

        if (!card.isFlipped()) { // 如果牌是未翻开的  
            // 将牌翻至正面  
            ImageView imageView = (ImageView) view;  
            imageView.setImageResource(card.getPatternResource());  
            card.flip(); // 更新牌的状态为已翻开  
        }  
    }  
}  

在这个修改后的 MainActivity.java 文件中,我们首先创建了一个 Game 对象,并在 onCreate() 方法中初始化了游戏。然后,在点击事件处理方法 onCardClicked() 中,我们使用 game.getCardAtIndex(index) 方法获取对应索引位置的 Card 对象,并根据其状态设置正面图案。


操作: 将代码复制粘贴进MainActivity.java,此时出现两个问题。

Card card = game.getCardAtIndex(index); // 获取对应的卡牌对象 

Card类并没有getCardAtIndex函数

imageView.setImageResource(card.getPatternResource());  

修改卡牌正面表现形式为getPatternResource(),但是该函数并没有定义。

因此我们询问大模型如何构造getCardAtIndex函数,将卡牌翻转方式修改为之前的形式,即将
```java
if (!card.isFlipped()) { // 如果牌是未翻开的  
        // 将牌翻至正面  
        ImageView imageView = (ImageView) view;  
        imageView.setImageResource(card.getPatternResource());  
        card.flip(); // 更新牌的状态为已翻开  
    }  
```java
这一部分代码修改回
```java
private int[] patternResources = {  
        R.drawable.card_pattern0,  
        R.drawable.card_pattern1,  
        R.drawable.card_pattern2,  
        R.drawable.card_pattern3,  
        R.drawable.card_pattern4,  
        R.drawable.card_pattern5,  
        R.drawable.card_pattern6,  
        R.drawable.card_pattern7  
};  

if (!card.isFlipped()) { // 如果牌是未翻开的  
            // 将牌翻至正面  
            ImageView imageView = (ImageView) view;  
            imageView.setImageResource(patternResources[card.getId()]);  
            card.flip(); // 更新牌的状态为已翻开  
        }  

交互4:获取卡片序号函数补全

用户:

请在game类中添加getCardAtIndex函数


ChatGPT:

好的,让我们在 Game 类中添加一个 getCardAtIndex() 方法来获取给定索引位置的卡牌对象。

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  

public class Game {  

    private List<Card> cards;  

    public Game() {  
        // 初始化牌堆  
        cards = new ArrayList<>();  
        for (int i = 0; i < 8; i++) {  
            cards.add(new Card(i)); // 每种图案添加一张牌  
            cards.add(new Card(i)); // 每种图案添加另一张牌  
        }  
        // 打乱牌堆顺序  
        Collections.shuffle(cards);  
    }  

    public List<Card> getCards() {  
        return cards;  
    }  

    public Card getCardAtIndex(int index) {  
        if (index >= 0 && index < cards.size()) {  
            return cards.get(index);  
        } else {  
            return null;  
        }  
    }  
}  

在这个修改后的 Game 类中,我们添加了一个 getCardAtIndex() 方法,该方法接受一个索引参数,并返回对应索引位置的卡牌对象。如果给定的索引超出了牌堆的范围,则返回 null

现在,您可以在 MainActivity.java 中使用 game.getCardAtIndex(index) 方法来获取给定索引位置的卡牌对象了。


操作:

将上述代码复制粘贴到Game.java中,此时的代码经过了较长时间的对话以及代码的修改,同时,为了增加代码的可读性,我们对代码进行了细微的重构。

为了确保ChatGPT对于完整代码的掌握,我们将代码全部发给ChatGPT


交互5:代码内容精修

用户:

这时候我们添加了一些代码,但是这些代码大模型还不知道,注意这里还有一个技巧,我们需要把代码给大模型,要让大模型知道。

我根据你的建议修改了Card Game MainActivity类,现在我把修改后的代码告诉你,请先不用回答。

MainActivity.java:

import androidx.appcompat.app.AppCompatActivity;   
import android.os.Bundle;   
import android.widget.ImageView;   
import android.widget.GridLayout;   
import android.view.View;   
import android.widget.ImageView;   

public class MainActivity extends AppCompatActivity {       
    private Game game;     // 图案资源数组      
   private int[] patternResources = {              
       R.drawable.card_pattern0,              
        R.drawable.card_pattern1,              
        R.drawable.card_pattern2,              
        R.drawable.card_pattern3,              
        R.drawable.card_pattern4,              
        R.drawable.card_pattern5,              
        R.drawable.card_pattern6,              
        R.drawable.card_pattern7      
    };     

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  

            // 创建游戏实例  
            game = new Game();  

            // 获取GridLayout  
        GridLayout gridLayout = findViewById(R.id.gridLayout);  

            // 获取GridLayout中的ImageView元素并设置背面图案  
            for (int i = 0; i < gridLayout.getChildCount(); i++) {  
                ImageView imageView = (ImageView) gridLayout.getChildAt(i);  
            imageView.setVisibility(View.VISIBLE);  
            imageView.setImageResource(R.drawable.card_back_image); // 设置为背面图案  
                imageView.setTag(i); // 将索引作为标记存储在ImageView中  
        }  
        }  

    public void onCardClicked(View view) {          
            int index = (int) view.getTag(); // 获取被点击的ImageView的索引          
            Card card = game.getCardAtIndex(index); // 获取对应的卡牌对象           
            if (!card.isFlipped()) { // 如果牌是未翻开的              
            // 将牌翻至正面              
           ImageView imageView = (ImageView) view;               
            imageView.setImageResource(patternResources[card.getId()]);              
            card.flip(); // 更新牌的状态为已翻开          
        }      
    }   
    }   

Game.java:

import java.util.ArrayList;   
import java.util.Collections;   
import java.util.List;   

public class Game {       
    private List<Card> cards;  

    public Game() {          
        // 初始化卡牌          
        initCards();      
        }  

        // 初始化卡牌      
        private void initCards() {          
            cards = new ArrayList<>();          
            for (int i = 0; i < 8; i++) {              
                cards.add(new Card(i));              
            cards.add(new Card(i)); // 每种图案复制一次,保证每种图案有两张卡牌          
            }          
            // 打乱列表顺序          
            Collections.shuffle(cards);      
        }       

        public Card getCardAtIndex(int index) {          
            if (index >= 0 && index < cards.size()) {              
                return cards.get(index);          
            } else {              
                return null;          
            }      
        }   
    }  

Card.java:

package com.example.card_game;   

public class Card {      
    private int pattern_id;      
    private boolean isFlipped;       
    public Card(int id) {          
        this.pattern_id = id;          
        this.isFlipped = false; // 默认情况下,牌是未翻开的      
        }       

        public int getId() {          
            return pattern_id;      
        }       

        public boolean isFlipped() {          
            return isFlipped;      
        }       

        public void flip() {          
            isFlipped = !isFlipped; // 翻转牌的状态      
        } 
    }  

ChatGPT:

看起来你已经很好地将代码进行了整理和修改。现在 MainActivityGame 类之间的交互看起来很清晰,而且 Card 类也在记录每张卡牌的信息方面工作正常。接下来,我们可以继续添加游戏的其他功能,比如检查两张牌是否匹配等。如果你需要任何帮助,请随时告诉我。


操作:

无操作


交互6:点击事件函数补全

用户:

现在请在onCardClicked函数添加代码完善功能:在用户点击牌时应检查该张牌是两张牌中的第几张,如果是第一张,则应当记录这张牌的index以及pattern_id;如果是第二张牌,则要与第一张牌比较,若pattern_id图案一致则消除这两张牌,若不一致则将这两张牌翻回背面


ChatGPT: 好的,让我们根据您的要求完善 onCardClicked() 方法,在用户点击牌时进行相应的逻辑处理。

public void onCardClicked(View view) {  
    int index = (int) view.getTag(); // 获取被点击的ImageView的索引  
    Card card = game.getCardAtIndex(index); // 获取对应的卡牌对象  

    if (!card.isFlipped()) { // 如果牌是未翻开的  
        // 将牌翻至正面  
        ImageView imageView = (ImageView) view;  
        imageView.setImageResource(patternResources[card.getId()]);  
        card.flip(); // 更新牌的状态为已翻开  

        // 检查是否是第一张牌  
        if (game.getFirstFlippedCardIndex() == -1) {  
            // 记录第一张牌的索引和图案id  
            game.setFirstFlippedCardIndex(index);  
            game.setFirstFlippedCardPattern(card.getId());  
        } else {  
            // 是第二张牌,获取第一张牌的信息  
            int firstFlippedIndex = game.getFirstFlippedCardIndex();  
            int firstFlippedPattern = game.getFirstFlippedCardPattern();  

            // 检查两张牌的图案是否一致  
            if (firstFlippedPattern == card.getId()) {  
                // 匹配成功,消除这两张牌  
                // 这里可以添加消除牌的动画效果等  
                game.removeMatchedCards(index, firstFlippedIndex);  
            } else {  
                // 匹配失败,将这两张牌翻回背面  
                // 这里可以添加翻回牌的动画效果等  
                game.flipCardsBack(index, firstFlippedIndex);  
            }  

            // 重置第一张牌的信息  
            game.resetFirstFlippedCard();  
        }  
    }  
}  

在这个修改后的 onCardClicked() 方法中,我们首先检查被点击的卡牌是否已经翻开。如果牌是未翻开的,我们将其翻至正面,并更新牌的状态为已翻开。

然后,我们检查是否是第一张牌。如果是第一张牌,我们记录下它的索引和图案 id。如果是第二张牌,我们获取第一张牌的信息,并与第二张牌进行比较。如果两张牌的图案一致,我们将它们消除,否则将它们翻回背面。

最后,无论是匹配成功还是失败,我们都重置了第一张牌的信息,以便下一次点击可以重新记录第一张牌的信息。


操作:

当代码复制粘贴进MainActivity.java文件中,修改onCardClicked函数,发现出现较多Game类中未定义的函数。


交互7:未定义函数补全

用户:

请根据新修改的onCardClicked() 方法完善game类,补充用到的函数


ChatGPT:

好的,让我们根据新的要求完善 Game 类,并添加必要的函数来处理卡牌匹配和翻转的逻辑。

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  

public class Game {  

    private List<Card> cards;  
    private int firstFlippedCardIndex;  
    private int firstFlippedCardPattern;  

    public Game() {  
        // 初始化卡牌  
        initCards();  
        firstFlippedCardIndex = -1; // 初始化为无效值  
        firstFlippedCardPattern = -1; // 初始化为无效值  
    }  

    // 初始化卡牌  
    private void initCards() {  
        cards = new ArrayList<>();  
        for (int i = 0; i < 8; i++) {  
            cards.add(new Card(i));  
            cards.add(new Card(i)); // 每种图案复制一次,保证每种图案有两张卡牌  
        }  
        // 打乱列表顺序  
        Collections.shuffle(cards);  
    }  

    public Card getCardAtIndex(int index) {  
        if (index >= 0 && index < cards.size()) {  
            return cards.get(index);  
        } else {  
            return null;  
        }  
    }  

    public int getFirstFlippedCardIndex() {  
        return firstFlippedCardIndex;  
    }  

    public int getFirstFlippedCardPattern() {  
        return firstFlippedCardPattern;  
    }  

    public void setFirstFlippedCardIndex(int index) {  
        firstFlippedCardIndex = index;  
    }  

    public void setFirstFlippedCardPattern(int pattern) {  
        firstFlippedCardPattern = pattern;  
    }  

    public void resetFirstFlippedCard() {  
        firstFlippedCardIndex = -1;  
        firstFlippedCardPattern = -1;  
    }  

    public void removeMatchedCards(int index1, int index2) {  
        // 将两张匹配的牌从列表中移除  
        if (index1 >= 0 && index1 < cards.size() && index2 >= 0 && index2 < cards.size()) {  
            cards.remove(index1);  
            // 由于第二张牌的索引会受到第一张牌的移除而变化,因此需要调整第二张牌的索引  
            if (index2 > index1) {  
                index2--;  
            }  
            cards.remove(index2);  
        }  
    }  

    public void flipCardsBack(int index1, int index2) {  
        // 将两张牌翻回背面  
        if (index1 >= 0 && index1 < cards.size() && index2 >= 0 && index2 < cards.size()) {  
            cards.get(index1).flip();  
            cards.get(index2).flip();  
        }  
    }  
}  

在这个修改后的 Game 类中,我们添加了几个新的方法来处理卡牌匹配和翻转的逻辑。

  • getFirstFlippedCardIndex()getFirstFlippedCardPattern() 方法用于获取第一张翻开的牌的索引和图案 id。
  • setFirstFlippedCardIndex()setFirstFlippedCardPattern() 方法用于设置第一张翻开的牌的索引和图案 id。
  • resetFirstFlippedCard() 方法用于重置第一张翻开的牌的信息。
  • removeMatchedCards() 方法用于移除匹配的两张牌。
  • flipCardsBack() 方法用于将不匹配的两张牌翻回背面。
  • 现在,Game 类中的功能已经完善,可以根据用户的点击事件来进行相应的卡牌匹配和翻转了。

操作:

将代码复制粘贴进Game.java,部分代码过于复杂,需要进行简化


交互8:复杂函数简化

用户:

removeMatchedCards()不用这么麻烦,只需在onCardClicked函数中,将imageView设置成隐身即可


ChatGPT:

明白了,如果您希望直接在点击事件处理函数中隐藏匹配的两张牌,我们可以简化 removeMatchedCards() 方法,只需要在 onCardClicked() 方法中直接隐藏对应的 ImageView 元素即可。

让我们来修改 onCardClicked() 方法和 Game 类:

public void onCardClicked(View view) {  
    int index = (int) view.getTag(); // 获取被点击的ImageView的索引  
    Card card = game.getCardAtIndex(index); // 获取对应的卡牌对象  

    if (!card.isFlipped()) { // 如果牌是未翻开的  
        // 将牌翻至正面  
        ImageView imageView = (ImageView) view;  
        imageView.setImageResource(patternResources[card.getId()]);  
        card.flip(); // 更新牌的状态为已翻开  

        // 检查是否是第一张牌  
        if (game.getFirstFlippedCardIndex() == -1) {  
            // 记录第一张牌的索引和图案id  
            game.setFirstFlippedCardIndex(index);  
            game.setFirstFlippedCardPattern(card.getId());  
        } else {  
            // 是第二张牌,获取第一张牌的信息  
            int firstFlippedIndex = game.getFirstFlippedCardIndex();  
            int firstFlippedPattern = game.getFirstFlippedCardPattern();  

            // 检查两张牌的图案是否一致  
            if (firstFlippedPattern == card.getId()) {  
                // 匹配成功,隐藏这两张牌  
                imageView.setVisibility(View.INVISIBLE);  
                ImageView firstFlippedView = (ImageView) ((GridLayout) view.getParent()).getChildAt(firstFlippedIndex);  
                firstFlippedView.setVisibility(View.INVISIBLE);  
            } else {  
                // 匹配失败,将这两张牌翻回背面  
                // 这里可以添加翻回牌的动画效果等  
                imageView.setImageResource(R.drawable.card_back_image);  
                ImageView firstFlippedView = (ImageView) ((GridLayout) view.getParent()).getChildAt(firstFlippedIndex);  
                firstFlippedView.setImageResource(R.drawable.card_back_image);  
                game.getCardAtIndex(firstFlippedIndex).flip(); // 更新第一张牌的状态为未翻开  
            }  

            // 重置第一张牌的信息  
            game.resetFirstFlippedCard();  
        }  
    }  
}  

在这个修改后的 onCardClicked() 方法中,当两张牌匹配成功时,我们直接将对应的 ImageView 元素设置为 INVISIBLE,从而隐藏这两张牌。这样就不需要在 Game 类中维护牌的列表了。


操作:

将代码复制粘贴进MainActivity.java文件,修改onCardClicked函数。

此时希望达成的效果是,如果翻出来的两张卡牌正面一致,则将这两张卡牌消除;如果不一致,则全都翻回背面。

但是出现的问题是,判定卡牌正面是否一致过于快速,在刚刚点击第二张牌的时候,还没有将第二张牌的正面显示出来,就出现了两张卡片消除或者第一张卡片翻回背面的情况。

因此,到这一步,配对消除的功能已经正常实现,只是缺少第二张牌翻到正面的效果,或者说只是缺少肉眼可见的效果。


交互9:卡牌翻转效果显示

用户:

我现在需要在查两张牌的图案是否一致且匹配失败的情况下加入代码,使得第二张牌的正面图案显示1秒钟后,将两张牌翻回背面;请利用imageView.setImageResource(patternResources[card.getId()]); 将牌翻至正面


ChatGPT:

好的,我们可以使用 Handler 来延迟执行将两张牌翻回背面的操作。下面是修改后的 onCardClicked() 方法:

import android.os.Handler;  

public void onCardClicked(View view) {  
    int index = (int) view.getTag(); // 获取被点击的ImageView的索引  
    Card card = game.getCardAtIndex(index); // 获取对应的卡牌对象  

    if (!card.isFlipped()) { // 如果牌是未翻开的  
        // 将牌翻至正面  
        ImageView imageView = (ImageView) view;  
        imageView.setImageResource(patternResources[card.getId()]);  
        card.flip(); // 更新牌的状态为已翻开  

        // 检查是否是第一张牌  
        if (game.getFirstFlippedCardIndex() == -1) {  
            // 记录第一张牌的索引和图案id  
            game.setFirstFlippedCardIndex(index);  
            game.setFirstFlippedCardPattern(card.getId());  
        } else {  
            // 是第二张牌,获取第一张牌的信息  
            int firstFlippedIndex = game.getFirstFlippedCardIndex();  
            int firstFlippedPattern = game.getFirstFlippedCardPattern();  

            // 检查两张牌的图案是否一致  
            if (firstFlippedPattern == card.getId()) {  
                // 匹配成功,隐藏这两张牌  
                imageView.setVisibility(View.INVISIBLE);  
                ImageView firstFlippedView = (ImageView) ((GridLayout) view.getParent()).getChildAt(firstFlippedIndex);  
                firstFlippedView.setVisibility(View.INVISIBLE);  
            } else {  
                // 匹配失败,将这两张牌翻回背面  
                // 这里可以添加翻回牌的动画效果等  
                Handler handler = new Handler();  
                handler.postDelayed(new Runnable() {  
                    @Override  
                    public void run() {  
                        imageView.setImageResource(R.drawable.card_back_image);  
                        ImageView firstFlippedView = (ImageView) ((GridLayout) view.getParent()).getChildAt(firstFlippedIndex);  
                        firstFlippedView.setImageResource(R.drawable.card_back_image);  
                        game.getCardAtIndex(firstFlippedIndex).flip(); // 更新第一张牌的状态为未翻开  
                    }  
                }, 1000); // 1秒后执行  
            }  

            // 重置第一张牌的信息  
            game.resetFirstFlippedCard();  
        }  
    }  
}  

在这个修改后的代码中,我们在匹配失败的情况下,使用 Handler 来延迟执行将两张牌翻回背面的操作。我们将 imageView.setImageResource() 的操作放在 Handlerrun() 方法中,并将延迟时间设置为 1000 毫秒(1 秒)。这样,第二张牌的正面图案会显示 1 秒钟后,再将两张牌翻回背面。


操作: 将代码复制粘贴至MainActivity.java中,替换onCardClicked函数。可以实现当两张卡牌不同时,将两张卡片都翻回背面的效果,如下图所示

5-7

5-8

5-9

也可以当两张卡牌相同时,将卡片消除

5-10

但是因为我们只要求匹配失败时,第二张卡牌显示1秒后翻回,对匹配成功时没有修改,所以当匹配成功时,依旧会出现,第二张卡牌还没有翻到正面,就已经被消除的情况,我们将在后面对于代码进行细微的修改以解决这个问题。


5.3.3 判断游戏结束

交互1:游戏结束判定

用户:

现在我们需要在检查两张牌的图案是否一致且匹配成功的情况下加入代码,当所有牌都匹配成功时,显示游戏结束界面


ChatGPT:

好的,让我们在匹配成功的情况下添加代码来检查是否所有的牌都已匹配成功,并显示游戏结束界面。我们可以在 onCardClicked() 方法中添加逻辑来实现这一功能。 首先,我们需要一个变量来跟踪已匹配的牌对数,以及总共的牌对数。然后,在每次匹配成功后,我们增加已匹配的牌对数,然后检查是否已匹配了所有的牌对。如果已匹配了所有的牌对,我们就可以显示游戏结束界面。

下面是修改后的 onCardClicked() 方法:

public void onCardClicked(View view) {  
    int index = (int) view.getTag(); // 获取被点击的ImageView的索引  
    Card card = game.getCardAtIndex(index); // 获取对应的卡牌对象  

    if (!card.isFlipped()) { // 如果牌是未翻开的  
        // 将牌翻至正面  
        ImageView imageView = (ImageView) view;  
        imageView.setImageResource(patternResources[card.getId()]);  
        card.flip(); // 更新牌的状态为已翻开  

        // 检查是否是第一张牌  
        if (game.getFirstFlippedCardIndex() == -1) {  
            // 记录第一张牌的索引和图案id  
            game.setFirstFlippedCardIndex(index);  
            game.setFirstFlippedCardPattern(card.getId());  
        } else {  
            // 是第二张牌,获取第一张牌的信息  
            int firstFlippedIndex = game.getFirstFlippedCardIndex();  
            int firstFlippedPattern = game.getFirstFlippedCardPattern();  

            // 检查两张牌的图案是否一致  
            if (firstFlippedPattern == card.getId()) {  
                // 匹配成功,隐藏这两张牌  
                imageView.setVisibility(View.INVISIBLE);  
                ImageView firstFlippedView = (ImageView) ((GridLayout) view.getParent()).getChildAt(firstFlippedIndex);  
                firstFlippedView.setVisibility(View.INVISIBLE);  

                // 增加已匹配的牌对数  
                game.incrementMatchedPairs();  

                // 检查是否所有的牌都已匹配成功  
                if (game.allPairsMatched()) {  
                    // 所有牌匹配成功,显示游戏结束界面  
                    showGameOver();  
                }  
            } else {  
                // 匹配失败,将这两张牌翻回背面  
                // 这里可以添加翻回牌的动画效果等  
                Handler handler = new Handler();  
                handler.postDelayed(new Runnable() {  
                    @Override  
                    public void run() {  
                        imageView.setImageResource(R.drawable.card_back_image);  
                        ImageView firstFlippedView = (ImageView) ((GridLayout) view.getParent()).getChildAt(firstFlippedIndex);  
                        firstFlippedView.setImageResource(R.drawable.card_back_image);  
                        game.getCardAtIndex(firstFlippedIndex).flip(); // 更新第一张牌的状态为未翻开  
                    }  
                }, 1000); // 1秒后执行  
            }  

            // 重置第一张牌的信息  
            game.resetFirstFlippedCard();  
        }  
    }  
}  

// 显示游戏结束界面的方法  
private void showGameOver() {  
    // 这里添加显示游戏结束界面的代码,可以是弹出对话框、跳转到另一个Activity等方式  
}  

在这个修改后的代码中,我们在匹配成功的情况下增加了已匹配的牌对数,并在每次匹配成功后检查是否所有的牌都已匹配成功。如果是,则调用 showGameOver() 方法来显示游戏结束界面。 showGameOver() 方法是一个私有方法,您可以根据您的需求来实现显示游戏结束界面的具体逻辑。


操作:

将代码复制粘贴进MainActivity.java中,但是此时Game类中缺少部分函数定义,同时showGameOver函数还未实现


交互2:未定义函数补全

用户:

请根据上述代码,在game类中增添方法


ChatGPT:

好的,我们需要在 Game 类中添加两个方法:

  1. incrementMatchedPairs():用于增加已匹配的牌对数。
  2. allPairsMatched():用于检查是否所有的牌都已匹配成功。

下面是修改后的 Game 类:

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  

public class Game {  

    private List<Card> cards;  
    private int firstFlippedCardIndex;  
    private int firstFlippedCardPattern;  
    private int matchedPairsCount;  
    private int totalPairsCount;  

    public Game() {  
        // 初始化卡牌  
        initCards();  
        firstFlippedCardIndex = -1; // 初始化为无效值  
        firstFlippedCardPattern = -1; // 初始化为无效值  
        matchedPairsCount = 0;  
        totalPairsCount = cards.size() / 2;  
    }  

    // 初始化卡牌  
    private void initCards() {  
        cards = new ArrayList<>();  
        for (int i = 0; i < 8; i++) {  
            cards.add(new Card(i));  
            cards.add(new Card(i)); // 每种图案复制一次,保证每种图案有两张卡牌  
        }  
        // 打乱列表顺序  
        Collections.shuffle(cards);  
    }  

    public Card getCardAtIndex(int index) {  
        if (index >= 0 && index < cards.size()) {  
            return cards.get(index);  
        } else {  
            return null;  
        }  
    }  

    public int getFirstFlippedCardIndex() {  
        return firstFlippedCardIndex;  
    }  

    public int getFirstFlippedCardPattern() {  
        return firstFlippedCardPattern;  
    }  

    public void setFirstFlippedCardIndex(int index) {  
        firstFlippedCardIndex = index;  
    }  

    public void setFirstFlippedCardPattern(int pattern) {  
        firstFlippedCardPattern = pattern;  
    }  

    public void resetFirstFlippedCard() {  
        firstFlippedCardIndex = -1;  
        firstFlippedCardPattern = -1;  
    }  

    public void incrementMatchedPairs() {  
        matchedPairsCount++;  
    }  

    public boolean allPairsMatched() {  
        return matchedPairsCount == totalPairsCount;  
    }  
}  

在这个修改后的 Game 类中,我们添加了 incrementMatchedPairs() 方法用于增加已匹配的牌对数,并添加了 allPairsMatched() 方法用于检查是否所有的牌都已匹配成功。


操作:

将代码全部复制粘贴进Game.java文件中,此时已经实现Game类被调用的所有函数,游戏基本成型。

但是此时游戏出现两个问题

  1. 部分卡牌在被翻开一次,然后因为没有配对成功被翻回去之后,在之后的回合里面,会无法被翻开。
  2. 两个卡牌如果配对成功,在刚点击第二张卡牌的时候,两张卡片就会被消除,第二张卡牌的正面依旧不能肉眼识别。

我们对onCardClicked函数以及Game类进行简单的修改如下来避免这个问题。

修改后代码如下:

onCardClicked函数

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  
import java.util.Random;  

public class Game {  

    private List<Card> cards;  
    private int firstFlippedCardIndex;  
    private int firstFlippedCardPattern;  
    private boolean firstCard;  
    private int matchedPairsCount;  

    public Game() {  
        // 初始化卡牌  
        initCards();  
        firstFlippedCardIndex = -1; // 初始化为无效值  
        firstFlippedCardPattern = -1; // 初始化为无效值  
        firstCard = true;  
        matchedPairsCount = 0;  
    }  

    // 初始化卡牌  
    private void initCards() {  
        cards = new ArrayList<>();  
        Random rand = new Random();  
        for (int i = 0; i < 8; i++) {  
            int randomNumber = rand.nextInt(8);  
            cards.add(new Card(randomNumber));  
            cards.add(new Card(randomNumber));  
        }  
        // 打乱列表顺序  
        Collections.shuffle(cards);  
    }  

    public Card getCardAtIndex(int index) {  
        if (index >= 0 && index < cards.size()) {  
            return cards.get(index);  
        } else {  
            return null;  
        }  
    }  

    public boolean getFirstCard(){  
        return firstCard;  
    }  

    public int getFirstFlippedCardIndex() {  
        return firstFlippedCardIndex;  
    }  

    public int getFirstFlippedCardPattern() {  
        return firstFlippedCardPattern;  
    }  

    public void changeFirstCard(){  
        firstCard = !firstCard;  
    }  

    public void setFirstFlippedCardIndex(int index) {  
        firstFlippedCardIndex = index;  
    }  

    public void setFirstFlippedCardPattern(int pattern) {  
        firstFlippedCardPattern = pattern;  
    }  

    public void resetFirstFlippedCard() {  
        firstFlippedCardIndex = -1;  
        firstFlippedCardPattern = -1;  
    }  

    public void flipCardsBack(int index1, int index2) {  
        // 将两张牌翻回背面  
        if (index1 >= 0 && index1 < cards.size() && index2 >= 0 && index2 < cards.size()) {  
            cards.get(index1).flip();  
            cards.get(index2).flip();  
        }  
    }  

    public void incrementMatchedPairs() {  
        matchedPairsCount++;  
    }  

    public boolean allPairsMatched() {  
        return ( matchedPairsCount == ( cards.size() / 2 ) );  
    }  
}  

Game.java文件

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  
import java.util.Random;  

public class Game {  

    private List<Card> cards;  
    private int firstFlippedCardIndex;  
    private int firstFlippedCardPattern;  
    private boolean firstCard;  
    private int matchedPairsCount;  

    public Game() {  
        // 初始化卡牌  
        initCards();  
        firstFlippedCardIndex = -1; // 初始化为无效值  
        firstFlippedCardPattern = -1; // 初始化为无效值  
        firstCard = true;  
        matchedPairsCount = 0;  
    }  

    // 初始化卡牌  
    private void initCards() {  
        cards = new ArrayList<>();  
        Random rand = new Random();  
        for (int i = 0; i < 8; i++) {  
            int randomNumber = rand.nextInt(8);  
            cards.add(new Card(randomNumber));  
            cards.add(new Card(randomNumber));  
        }  
        // 打乱列表顺序  
        Collections.shuffle(cards);  
    }  

    public Card getCardAtIndex(int index) {  
        if (index >= 0 && index < cards.size()) {  
            return cards.get(index);  
        } else {  
            return null;  
        }  
    }  

    public boolean getFirstCard(){  
        return firstCard;  
    }  

    public int getFirstFlippedCardIndex() {  
        return firstFlippedCardIndex;  
    }  

    public int getFirstFlippedCardPattern() {  
        return firstFlippedCardPattern;  
    }  

    public void changeFirstCard(){  
        firstCard = !firstCard;  
    }  

    public void setFirstFlippedCardIndex(int index) {  
        firstFlippedCardIndex = index;  
    }  

    public void setFirstFlippedCardPattern(int pattern) {  
        firstFlippedCardPattern = pattern;  
    }  

    public void resetFirstFlippedCard() {  
        firstFlippedCardIndex = -1;  
        firstFlippedCardPattern = -1;  
    }  

    public void flipCardsBack(int index1, int index2) {  
        // 将两张牌翻回背面  
        if (index1 >= 0 && index1 < cards.size() && index2 >= 0 && index2 < cards.size()) {  
            cards.get(index1).flip();  
            cards.get(index2).flip();  
        }  
    }  

    public void incrementMatchedPairs() {  
        matchedPairsCount++;  
    }  

    public boolean allPairsMatched() {  
        return ( matchedPairsCount == ( cards.size() / 2 ) );  
    }  
}  

交互3:游戏结束显示

用户:

请完成showGameOver函数,弹出对话框1秒显示“恭喜,游戏结束!”


ChatGPT:

好的,让我们来完成 showGameOver() 方法,以弹出对话框显示“恭喜,游戏结束!”并在 1 秒后关闭对话框。

import android.app.AlertDialog;  
import android.content.DialogInterface;  
import android.os.Handler;  

public class MainActivity extends AppCompatActivity {  

    // ... 其他代码 ...  

    // 显示游戏结束界面的方法  
    private void showGameOver() {  
        AlertDialog.Builder builder = new AlertDialog.Builder(this);  
        builder.setMessage("恭喜,游戏结束!");  

        // 创建对话框并显示  
        AlertDialog dialog = builder.create();  
        dialog.show();  

        // 1秒后关闭对话框  
        Handler handler = new Handler();  
        handler.postDelayed(new Runnable() {  
            @Override  
            public void run() {  
                dialog.dismiss(); // 关闭对话框  
            }  
        }, 1000); // 1秒后执行  
    }  
}  

在这个修改后的代码中,我们使用 AlertDialog.Builder 创建一个简单的对话框,并设置其消息为“恭喜,游戏结束!”。然后,我们创建对话框并显示出来。接着,我们使用 Handler 来延迟 1 秒后关闭对话框,以达到我们的需求。


操作:

将代码复制粘贴进MainActivity.java,完成showGameOver函数编写。

此时游戏可以正常进行到最后,实现卡牌全部消除,并且在全部消除之后弹出“恭喜,游戏结束!”的对话框,如下图所示:

5-11

5.3.4 游戏重置

交互1:重置函数实现

用户:

现在,我们需要实现重置按钮,重置函数为onRestartClicked,你需要在MainActivity中实现这一函数,功能是:在点击这一按钮时,调用:

game = new Game();  

// 获取GridLayout  
GridLayout gridLayout = findViewById(R.id.gridLayout);  

// 获取GridLayout中的ImageView元素并设置背面图案  
for (int i = 0; i < gridLayout.getChildCount(); i++) {  
    ImageView imageView = (ImageView) gridLayout.getChildAt(i);  
    imageView.setVisibility(View.VISIBLE);  
    imageView.setImageResource(R.drawable.card_back_image); // 设置为背面图案  
    imageView.setTag(i); // 将索引作为标记存储在ImageView中  12.   } 

ChatGPT:

好的,让我们在 MainActivity 中实现重置按钮的功能,即在点击重置按钮时,重新创建游戏实例,并将所有卡牌翻回背面。

public class MainActivity extends AppCompatActivity {  

    // ... 其他代码 ...  

    // 重置按钮点击事件处理函数  
    public void onRestartClicked(View view) {  
        // 创建新的游戏实例  
        game = new Game();  

        // 获取GridLayout  
        GridLayout gridLayout = findViewById(R.id.gridLayout);  

        // 获取GridLayout中的ImageView元素并设置背面图案  
        for (int i = 0; i < gridLayout.getChildCount(); i++) {  
            ImageView imageView = (ImageView) gridLayout.getChildAt(i);  
            imageView.setVisibility(View.VISIBLE);  
            imageView.setImageResource(R.drawable.card_back_image); // 设置为背面图案  
            imageView.setTag(i); // 将索引作为标记存储在ImageView中  
        }  
    }  
}  

在这个修改后的代码中,我们实现了名为 onRestartClicked() 的函数,用于处理重置按钮的点击事件。在该函数中,我们首先创建了一个新的游戏实例 game = new Game()。然后,我们获取 GridLayout 中的每个 ImageView 元素,并将它们的可见性设置为可见,同时将它们的图案设置为背面图案。这样就完成了重置按钮的功能。


操作:

将代码复制粘贴进MainActivity中,实现重置功能 在游戏过程中的任意环节,点击“重置”按钮,可以重新开始游戏

5-12

5-13


5.3.5 细节优化

交互1:细节设计完善

用户:

我希望你实现在检查两张牌的图案是否一致后的handler.postDelayed的0.5秒内,如果我点击了其他卡片动作无效


ChatGPT:

明白了,你希望在检查两张牌的图案是否一致后,在 0.5 秒的延迟期间,如果玩家点击了其他卡片,那么该点击动作应该无效。让我们修改代码以实现这一功能。

我们可以在点击其他卡片时,先检查是否处于匹配等待期间,如果是,则不执行点击动作。我们可以通过添加一个标志来实现这一点。

public class MainActivity extends AppCompatActivity {  

    private Game game;  
    private boolean matchingInProgress = false; // 标志是否处于匹配等待期间  

    // ... 其他代码 ...  

    public void onCardClicked(View view) {  
        if (matchingInProgress) {  
            // 如果处于匹配等待期间,点击动作无效  
            return;  
        }  

        int index = (int) view.getTag(); // 获取被点击的ImageView的索引  
        Card card = game.getCardAtIndex(index); // 获取对应的卡牌对象  

        if (!card.isFlipped()) { // 如果牌是未翻开的  
            // 将牌翻至正面  
            ImageView imageView = (ImageView) view;  
            imageView.setImageResource(patternResources[card.getId()]);  
            card.flip(); // 更新牌的状态为已翻开  

            // 检查是否是第一张牌  
            if (game.getFirstCard()) {  
                // 记录第一张牌的索引和图案id  
                game.changeFirstCard();  
                game.setFirstFlippedCardIndex(index);  
                game.setFirstFlippedCardPattern(card.getId());  
            } else {  
                // 是第二张牌,获取第一张牌的信息  
                game.changeFirstCard();  
                int firstFlippedIndex = game.getFirstFlippedCardIndex();  
                int firstFlippedPattern = game.getFirstFlippedCardPattern();  

                // 检查两张牌的图案是否一致  
                if (firstFlippedPattern == card.getId()) {  
                    // 匹配成功,消除这两张牌  
                    // 这里可以添加消除牌的动画效果等  
                    matchingInProgress = true; // 进入匹配等待期间  
                    Handler handler = new Handler();  
                    handler.postDelayed(new Runnable() {  
                        @Override  
                        public void run() {  
                            imageView.setVisibility(View.INVISIBLE);  
                            ImageView firstFlippedView = (ImageView) ((GridLayout) view.getParent()).getChildAt(firstFlippedIndex);  
                            firstFlippedView.setVisibility(View.INVISIBLE);  
                            game.incrementMatchedPairs();  
                            if (game.allPairsMatched()) {  
                                // 所有牌匹配成功,显示游戏结束界面  
                                showGameOver();  
                            }  
                            matchingInProgress = false; // 退出匹配等待期间  
                        }  
                    }, 500); // 0.5秒后执行  
                }  
                else {  
                    // 匹配失败,将这两张牌翻回背面  
                    matchingInProgress = true; // 进入匹配等待期间  
                    Handler handler = new Handler();  
                    handler.postDelayed(new Runnable() {  
                        @Override  
                        public void run() {  
                            game.flipCardsBack(index, firstFlippedIndex);  
                            imageView.setImageResource(R.drawable.card_back_image);  
                            ImageView firstFlippedView = (ImageView) ((GridLayout)view.getParent()).getChildAt(firstFlippedIndex);  
                            firstFlippedView.setImageResource(R.drawable.card_back_image);  
                            matchingInProgress = false; // 退出匹配等待期间  
                        }  
                    }, 500); // 0.5秒后执行  
                }  

                // 重置第一张牌的信息  
                game.resetFirstFlippedCard();  
            }  
        }  
    }  

    // ... 其他代码 ...  
}  

在这个修改后的代码中,我们添加了一个名为 matchingInProgress 的布尔变量,用于标志是否处于匹配等待期间。当点击其他卡片时,我们首先检查这个变量。如果为 true,说明当前正在进行匹配等待期间,此时点击动作无效,直接返回。这样就实现了在匹配等待期间点击其他卡片时动作无效的功能。


操作: 将代码复制粘贴进MainActiviy.java文件中进行相应修改,可以发现,在两个卡牌进行匹配确认的时间内,点击其他卡牌的操作无效。

至此,纸牌配对游戏开发完成,可以正确完成所有任务。