《今天谁也别想拯救船长》纯kotlin 实现拼图游戏

1,400 阅读4分钟

“我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛

开局一张图,第一关

  • 只可点击挨着缺失部分的图片,点击即为和缺失部分互换。 微信图片_20220423140457.jpg

过关显示缺失图片

  • 我用了264秒,你呐 微信图片_20220423115138.jpg

零、 体验方式

  • 安卓手机扫码下载体验

微信图片_20220423114754.png

一、创意灵感

  • 专属90后的回忆,当年大家一定都玩过一种拼图游戏
    • 整个图片是缺少一块的,用于移动其余打乱的图片,最终所有图片移动到对应的位置时缺少的图片将显示,并通关成功!
  • 为了增加趣味性,选择使用船长来作为拼图的图片 微信图片_20220423092656.png
  • 由于船长头像分割到4X4的时候会出现多个黑色方块,所以图片设计成了这个样子,此处感谢南方者提供的图片

游戏规则

  • 只可点击空白区域周边的图片点击的图片即为想移动到空白位置的图片
  • 基础功能还是将打乱的图恢复到原始位置
  • 增加了关卡,第一关是3x3第二关是4x4第三关是5x5第四关是6x6第五关是8x8
  • 五关分别对应新手、入门、高手、变态、超神
  • 通过五关才算拯救船长成功!(为降低难度,目前开放前两关,通过即拯救船长)

二、思路分析

  • 拼图的核心是一个nxn的矩阵,点击矩阵中的图片
    • 判断点击的图片第几行我们/n(除法)
    • 判断点击的图片第几列我们%n(取余)
    • 判断空白图片所在的行列同上
    • 两两相减,进行判断
    • 如果在同一行,列数相减,绝对值为1,可移动
    • 如果在同一列,行数相减,绝对值为1,可移动
    • 其他情况不符合条件不进行移动
  • 界面设计(安卓屏幕多尺寸适配)——ConstraintLayout符合
  • 开始撸码

三、AC 代码:

  • 首先是布局
    • 还没有用过ConstraintLayout的建议补一下我之前的这篇文章ConstraintLayout+ViewPager2打造《摇一摇新年幸运签》App这里就不多介绍了
    • 讲一下需要注意的点,约束布局至少需要两个方向添加约束,若你想均分就和我一样左右都添加约束即可
    • 宽高比可通过layout_constraintDimensionRatio属性设置
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/design_default_color_error"
    android:padding="20dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:paddingBottom="10dp"
        android:text="时间 : 0"
        android:textColor="#FF0000"
        android:textSize="20sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <ImageView
        android:id="@+id/x00"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@mipmap/xy9_01"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/x01"
        app:layout_constraintTop_toBottomOf="@id/tv_time" />

    <ImageView
        android:id="@+id/x01"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@mipmap/xy9_02"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toRightOf="@id/x00"
        app:layout_constraintRight_toLeftOf="@id/x02"
        app:layout_constraintTop_toBottomOf="@id/tv_time" />

    <ImageView
        android:id="@+id/x02"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@mipmap/xy9_03"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toRightOf="@id/x01"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_time" />

    <ImageView
        android:id="@+id/x10"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@mipmap/xy9_04"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/x01"
        app:layout_constraintTop_toBottomOf="@id/x00" />

    <ImageView
        android:id="@+id/x11"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@mipmap/xy9_05"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toRightOf="@id/x00"
        app:layout_constraintRight_toLeftOf="@id/x02"
        app:layout_constraintTop_toBottomOf="@id/x01" />

    <ImageView
        android:id="@+id/x12"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@mipmap/xy9_06"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toRightOf="@id/x01"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/x02" />

    <ImageView
        android:id="@+id/x20"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@mipmap/xy9_07"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/x01"
        app:layout_constraintTop_toBottomOf="@id/x10" />

    <ImageView
        android:id="@+id/x21"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@mipmap/xy9_08"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toRightOf="@id/x00"
        app:layout_constraintRight_toLeftOf="@id/x02"
        app:layout_constraintTop_toBottomOf="@id/x11" />

    <ImageView
        android:id="@+id/x22"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@mipmap/xy9_09"
        android:visibility="invisible"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toRightOf="@id/x01"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/x12" />

    <Button
        android:id="@+id/btn_restart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="restart"
        android:layout_gravity="center"
        android:text="重新开始"
        android:backgroundTint="#ff0000"
        android:background="@null"
        android:layout_marginTop="20dp"
        app:layout_constraintTop_toBottomOf="@id/x20"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
  • 拼图移动判断核心代码
        //判断选中的图片在第几行
        val sitex = site / imageX
        //判断选中的图片在第几列
        val sitey = site % imageY 
        //获取空白区域的行
        val blankx = blankSwap / imageX
        //获取空白区域的列
        val blanky = blankSwap % imageY
        //相减取绝对值
        val x = abs(sitex - blankx)
        val y = abs(sitey - blanky)
        //1.在同一行,列数相减,绝对值为1,可移动   2.在同一列,行数相减,绝对值为1,可以移动
        if (x == 0 && y == 1 || y == 0 && x == 1) {
            //通过id,查找到这个可以移动的按钮
            val clickButton: ImageView = findViewById(imageButtonId)
            //点击的位置变成空白
            clickButton.visibility = View.INVISIBLE
            //查找到空白区域的按钮
            val blankButton: ImageView = findViewById(blankImgId)
            //将空白区域的按钮设置图片
            blankButton.setImageResource(image[imageIndex[site]])
            //移动之前是不可见的,移动之后,将控件设置为可见
            blankButton.visibility = View.VISIBLE
            //将改变角标的过程记录到存储图片位置数组当中
            swap(site, blankSwap)
            //新的空白区域位置更新等于传入的点击按钮的位置
            blankSwap = site
            blankImgId = imageButtonId
            //定义标志位
            var loop = true 
            //循环判断位置是否匹配,不匹配则将标志位置为false
            for (i in imageIndex.indices) {
            if (imageIndex[i] != i) {
                loop = false
                break
            }
        }
        if (loop) {
            //拼图成功了
            //停止计时
            handler.removeMessages(1)
            //拼图成功后,禁止玩家继续移动按钮
            xy00.isClickable = false
            xy01.isClickable = false
            xy02.isClickable = false
            xy10.isClickable = false
            xy11.isClickable = false
            xy12.isClickable = false
            xy20.isClickable = false
            xy21.isClickable = false
            xy22.isClickable = false
            xy22.setImageResource(image[8])
            xy22.visibility = View.VISIBLE
            //弹出提示用户成功的对话框
            val builder: AlertDialog.Builder = AlertDialog.Builder(this)
            builder.setMessage("恭喜,拯救船长成功!您用的时间为" + time + "秒")
                .setPositiveButton("确认", null)
            val dialog: AlertDialog = builder.create()
            dialog.show()

四、总结:

  • 好了,代码撸完,我们总结一下
    • 首先我们做了屏幕适配

    • 除法和取余的运用

    • 最后不要忘记判断是否成功

好啦,心动不如行动,快来拯救船长吧!