[Android]自己动手做个拼图游戏

摘要:
将拼图块随机打乱,并保证其能有解。想拉伸成屏幕大小,首先要知道屏幕的大小,Android获得屏幕大小的代码如下:DisplayMetricsmetrics=newDisplayMetrics();getWindowManager().getDefaultDisplay().getRealMetrics;//sdk17+intscreenWidth=metrics.widthPixels;//屏幕宽intscreenHeight=metrics.heightPixels;//屏幕高将图片拉伸到屏幕大小Bitmapback=Bitmap.createScaledBitmap;将图片切成若干块privatefinalintCOL=3;//列,默认3列privatefinalintROW=3;//行,默认3行inttileWidth=back.getWidth()/COL;//每一块的宽inttileHeight=back.getHeight()/ROW;//每一块的高Bitmap[]bitmapTiles=newBitmap[COL*ROW];intidx=0;for{for{bitmapTiles[idx++]=Bitmap.createBitmap;}}2.将拼图块随机打乱,并保证其能有解。isSuccess)continue;canvas.drawBitmap;}}}移动块也很简单,当点击屏幕时,计算其在拼图数据中对应的索引。

目标

在做这个游戏之前,我们先定一些小目标列出来,一个一个的解决,这样,一个小游戏就不知不觉的完成啦。我们的目标如下:

  1. 游戏全屏,将图片拉伸成屏幕大小,并将其切成若干块。
  2. 将拼图块随机打乱,并保证其能有解。
  3. 在屏幕上留出一个空白块,当点空白块旁边的块,将这块移动到空白块。
  4. 判断是否已经拼好。

动图

实现目标

1.将图片拉伸成屏幕大小,并将其切成若干块。

想拉伸成屏幕大小,首先要知道屏幕的大小,Android获得屏幕大小的代码如下:

DisplayMetrics metrics =new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(metrics);//sdk17+
int screenWidth = metrics.widthPixels;//屏幕宽
int screenHeight = metrics.heightPixels;//屏幕高

将图片拉伸到屏幕大小

Bitmap back=Bitmap.createScaledBitmap(bitmap,
MainActivity.getScreenWidth(),
MainActivity.getScreenHeight(),
true);

将图片切成若干块

 private final int COL=3;//列,默认3列
 private final int ROW=3;//行,默认3行
 int tileWidth=back.getWidth()/COL;//每一块的宽
 int tileHeight=back.getHeight()/ROW;//每一块的高
 Bitmap[] bitmapTiles =new Bitmap[COL*ROW];
 int idx=0;
 for(int i=0;i<ROW;i++)
 {
     for(int j=0;j<COL;j++)
     {
         bitmapTiles[idx++]=Bitmap.createBitmap(back,
         j*tileWidth,
         i*tileHeight,
		tileWidth,tileHeight);
     }
 }

2. 将拼图块随机打乱,并保证其能有解。

这个问题应该是这个小游戏的核心了,有些人在做拼图的时候就随便乱摆,最后发现拼不回来,超级尴尬。要想打乱了还能拼回来,我们呢,就想到了模拟人打乱拼图的方法,就是将空白块与旁边的非空白块交换位置,与旁边哪个非空白块交换是随机的,然后将这个过程重复若干次,重复的次数也是随机的,这样一来,保证了图块的随机,又保证了能拼回来。在这里我们用数字0到N-1(N为块的数量)表示每一块,并用二维数组存储他们。

    private void createIntegerArray(int row,int col)
    {
        array=new int[row][col];
        int idx=0;
        for(int i=0;i<row;i++)
        for(int j=0;j<col;j++)
            array[i][j]=idx++;
    }

下面是打乱块的算法,最后一块是空白块,让它随机与旁边的某一块进行交换,这个过程中要检查数组边界,不要让它越界。

    //四个方向
   private int[][] dir={
        {0,1},//下
        {1,0},//右
        {0,-1},//上
        {-1,0}//左
    };
    /**
     * 移动块的位置
     * @param srcX 初始x位置
     * @param srcY 初始y位置
     * @param xOffset x偏移量
     * @param yOffset y偏移量
     * @return 新的位置,错误返回new Point(-1,-1);
     */
    private Point move(int srcX,int srcY,int xOffset,int yOffset)
    {
        int x=srcX+xOffset;
        int y=srcY+yOffset;
        if(x<0||y<0||x>=col||y>=row)
            return new Point(-1,-1);

        int temp=array[y][x];
        array[y][x]=array[srcY][srcX];
        array[srcY][srcX]=temp;

        return new Point(x,y);
    }

    /**
     * 得到下一个可以移动的位置
     * @param src 初始的点
     * @return
     */
    private  Point getNextPoint(Point src)
    {
        Random rd=new Random();
        int idx=rd.nextInt(4);//,因为有4个方向,所以产生0~3的随机数
        int xOffset=dir[idx][0];
        int yOffset=dir[idx][1];
        Point newPoint=move(src.getX(),src.getY(),xOffset,yOffset);
        if(newPoint.getX()!=-1&&newPoint.getY()!=-1) {
            return newPoint;//找到了新的点
        }

       return getNextPoint(src);//没有找到,继续
    }

    /**
     * 生成拼图数据
     * @param row
     * @param col
     * @return
     */
    public int[][] createRandomBoard(int row,int col)
    {
        if(row<2||col<2)
            throw new IllegalArgumentException("行和列都不能小于2");
        this.row=row;
        this.col=col;
        createIntegerArray(row,col);//初始化拼图数据
        int count=0;
        Point tempPoint=new Point(col-1,row-1);//最后一块是空白块
        Random rd=new Random();
        int num=rd.nextInt(100)+20;//产生20~119的随机数,表示重复的次数
        while (count<num)
       {
           tempPoint=getNextPoint(tempPoint);//获得下个点,并更新空白块位置
           count++;
        }
        return  array;
    }

3. 在屏幕上留出一个空白块,当点空白块旁边的块,将这块移动到空白块。

留出空白块很简单,由于上面我们将最后一块作为空白块。当我们绘图时,略过它即可。代码实现如下:

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.GRAY);
        for(int i=0;i<ROW;i++) {
            for (int j = 0; j < COL; j++) {
                int idx=dataTiles[i][j];
                if(idx==ROW*COL-1&&!isSuccess)
                    continue;
                canvas.drawBitmap(bitmapTiles[idx],
                j*tileWidth,
                i*tileHeight,paint);
            }
        }
    }

移动块也很简单,当点击屏幕时,计算其在拼图数据中对应的索引。当计算到点击非空白块就寻找它旁边有没有空白块,有,则将拼图数据中表示空白块和非空白块的数据交换,并刷新View即可

    /**
     * 将屏幕上的点转换成,对应拼图块的索引
     * @param x
     * @param y
     * @return
     */
    private Point xyToIndex(int x,int y)
    {
        int extraX=x%tileWidth>0?1:0;
        int extraY=x%tileWidth>0?1:0;
        int col=x/tileWidth+extraX;
        int row=y/tileHeight+extraY;

        return new Point(col-1,row-1);
    }
    /**
    *点击屏幕时发生
    */
	 @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction()==MotionEvent.ACTION_DOWN) {
            Point point = xyToIndex((int) event.getX()
            , (int) event.getY());

            for(int i=0;i<dir.length;i++)
            {
                int newX=point.getX()+dir[i][0];
                int newY=point.getY()+dir[i][1];

                if(newX>=0&&newX<COL&&newY>=0&&newY<ROW){
                    if(dataTiles[newY][newX]==COL*ROW-1)
                    {
                        int temp=dataTiles[point.getY()][point.getX()];
                        dataTiles[point.getY()][point.getX()]=dataTiles[newY][newX];
                        dataTiles[newY][newX]=temp;
                        invalidate();
                    }
                }
            }
        }
        return true;
    }

4. 判断是否已经拼好

我们初始化数据时,是从0开始,依次增加作为拼图数据。当拼好的时候,拼图数据也应该是一样的,所以我们比较数组中每一个数据与它的下一个数据,如果每一个数据都小于它的下一个数据,说明数组里面的数据已经从小到大排列好。

/**
     * 判断是否拼图成功
     * @param arr
     * @return
     */
    public boolean isSuccess(int[][] arr)
    {
        int idx=0;

        for(int i=0;i<arr.length;i++)
        {
            for(int j=0;j<arr[i].length&&idx<row*col-1;j++)
            {

                if(arr[idx/row][idx%col]>arr[(idx+1)/row][(idx+1)%col])
                {

                    return false;

                }
                idx++;
            }

        }
        return  true;
    }

拼图游戏技巧

拼图技巧觉得也有必要说一下,不然有些人就会说:你的算法有问题,这根本拼不好!我也是超级无奈啊!拼图的技巧是,我们先把上面的第一行拼好,然后再把第二行拼好,这样,一直下去~就能完全拼好了。

总结

这个小游戏简单,可以拿来练手,还可以拿来装(liao)逼(mei),如果不会,何乐而不看呢。这个小游戏也是将视图和数据分开,代码容易移植。

项目地址

https://github.com/luoyesiqiu/PuzzleGame

免责声明:文章转载自《[Android]自己动手做个拼图游戏》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Jboss安装FPM八:FPM TREE下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

spring-kafka生产者消费者配置详解

一、生产者1、重要配置    # 高优先级配置    # 以逗号分隔的主机:端口对列表,用于建立与Kafka群集的初始连接    spring.kafka.producer.bootstrap-servers=TopKafka1:9092,TopKafka2:9092,TopKafka3:9092         # 设置大于0的值将使客户端重新发送任何数...

Spring Boot Actutaur + Telegraf + InFluxDB + Grafana 构建监控平台

完成一套精准,漂亮图形化监控系统从这里开始第一步 为啥选择这些组件 Jolokia: Spring Boot 认可使用Jolokia来通过HTTP导出export JMX数据。你只需要在工程类路径中增加一些依赖项,一切都是开箱即用的。不需要任何额外的实现。 Telegraf: Telegraf支持通过整合Jolokia来集成JMX数据的收集。它有一个预...

K8S部署之kubeadm

K8S部署笔记 一、集群环境说明 主机名 IP地址 说明 k8s-master01 192.168.1.107 master节点 k8s-master02 192.168.1.108 master节点 k8s-master03 192.168.1.109 master节点 k8s-master-lb(在master节点) 192.16...

常规服务器配置:Prometheus+Grafana监控

准备两台测试环境: 主:192.168.28.130 备:192.168.28.131 博文大纲:一、prometheus简介二、Prometheus组成及架构三、部署prometheus1)环境准备2)部署prometheus3)配置Peometheus监控实现报警 一、prometheus简介 Prometheus是一套开源的系统监控报警框架。它以给定...

Unity3D去掉全屏时的屏幕黑边

给全屏后不在乎拉伸变形仍想让画面占满屏幕的朋友,网上搜了一上午,实在是没有相关的资料,只能自己琢磨了。 使用Canvas Scaler在全屏后Unity虽然会为我们自动拉伸UI,但拉伸后仍然保持我们在Unity中设置的高宽比。屏幕中的黑边需要通过代码来对UI的内容进行拉伸填充。 下面是我的测试环境: Unity3D 5.3.4 Win 10 64bit 测...

CSS3 border-image详解、应用

一、border-image的兼容性 border-image可以说是CSS3中的一员大将,将来一定会大放光彩,其应用潜力真的是非常的惊人。可惜目前支持的浏览器有限,仅Firefox3.5,chrome浏览器,Safari3+支持border-image。所以,就本文而言,IE浏览器可以回家休息了,Firefox3及其以下以及Opera浏览器也可以休息去看...