helloPe的android项目实战之连连看—实现篇(一)

  在上一篇文章helloPe的android项目实战之连连看—设计篇中,我们进行了对android中连连看的项目的设计,包括功能模块的划分以及核心算法的设计。此文章接上文对android平台连连看程序进入实现阶段。在此项目中,根据上文中对于功能的分析,我们将实现以下类(下面即是工程的文件目录):

                                        

在开发中,我们遵循由下向上的方式,也就是说,我们首先开发位于最底层的类,这种类并不依赖于其他的我们需要实现的类。根据上文的分析,首先我们开 发在表示层模块中的界面显示类,首先是BoardView类,在android平台下,采用继承自View类的方式,看此类的代码,代码中尽量添加了详细 的注释:

1 package nate.llk.view;
2  
3 /*导入包种种再次略去*/
001 /**
002  * **********************************************
003  * @author HelloPe
004  ************************************************
005  */
006 public class BoardView extends View {
007     /**
008      * xCount x轴方向的图标数+2
009      */
010     protected static final int xCount = 10;
011     /**
012      * yCount y轴方向的图表数+2
013      */
014     protected static final int yCount = 12;
015     /**
016      * map 连连看游戏棋盘,map中添加的int型在程序中的意思是index,而不是屏幕坐标!
017      */
018     protected int[][] map = new int[xCount][yCount];
019     /**
020      * iconSize 图标大小,图标是正方形,所以一个int变量表示即可
021      */
022     protected int iconSize;
023     /**
024      * iconCounts 图标的数目
025      */
026     protected int iconCounts=19;
027     /**
028      * icons 所有的图片
029      */
030     protected Bitmap[] icons = new Bitmap[iconCounts];
031     /**
032      * path 可以连通点的路径
033      */
034     private Point[] path = null;
035     /**
036      * selected 选中的图标
037      */
038     protected List<Point> selected = new ArrayList<Point>();
039      
040     /**
041      * 构造函数
042      * @param context
043      * @param attrs
044      */
045     public BoardView(Context context, AttributeSet attrs) {
046         super(context, attrs);
047         calIconSize();
048         Resources r = getResources();
049         //载入连连看中的图标资源
050         loadBitmaps(1, r.getDrawable(R.drawable.fruit_01));
051         loadBitmaps(2, r.getDrawable(R.drawable.fruit_02));
052         loadBitmaps(3, r.getDrawable(R.drawable.fruit_03));
053         loadBitmaps(4, r.getDrawable(R.drawable.fruit_04));
054         loadBitmaps(5, r.getDrawable(R.drawable.fruit_05));
055         loadBitmaps(6, r.getDrawable(R.drawable.fruit_06));
056         loadBitmaps(7, r.getDrawable(R.drawable.fruit_07));
057         loadBitmaps(8, r.getDrawable(R.drawable.fruit_08));
058         loadBitmaps(9, r.getDrawable(R.drawable.fruit_09));
059         loadBitmaps(10, r.getDrawable(R.drawable.fruit_10));
060         loadBitmaps(11, r.getDrawable(R.drawable.fruit_11));
061         loadBitmaps(12, r.getDrawable(R.drawable.fruit_12));
062         loadBitmaps(13, r.getDrawable(R.drawable.fruit_13));
063         loadBitmaps(14, r.getDrawable(R.drawable.fruit_14));
064         loadBitmaps(15, r.getDrawable(R.drawable.fruit_15));
065         loadBitmaps(16, r.getDrawable(R.drawable.fruit_17));
066         loadBitmaps(17, r.getDrawable(R.drawable.fruit_18));
067         loadBitmaps(18, r.getDrawable(R.drawable.fruit_19));
068     }
069     /**
070      * 计算图标的大小
071      */
072     private void calIconSize(){
073         //取得屏幕的大小
074         DisplayMetrics dm = new DisplayMetrics();
075         ((Activity) this.getContext()).getWindowManager()
076         .getDefaultDisplay().getMetrics(dm);
077         iconSize = dm.widthPixels/( xCount );
078     }
079     /**
080      * 函数目的在于载入图标资源,同时将一个key(特定的整数标识)与一个图标进行绑定
081      * @param key 特定图标的标识
082      * @param d drawable下的资源
083      */
084     public void loadBitmaps(int key,Drawable d){
085         Bitmap bitmap = Bitmap.createBitmap(iconSize,iconSize,Bitmap.Config.ARGB_8888);
086         Canvas canvas = new Canvas(bitmap);
087         d.setBounds(0, 0, iconSize, iconSize);
088         d.draw(canvas);
089         icons[key]=bitmap;  //未用0 号index
090     }
091     /**
092      * View自带的,但是在此方法中,有画路径(删除联通的两个图标),
093      * 绘制棋盘的所有图标(也可理解为刷新,只要此map位置值>0)
094      * 放大第一个选中的图标(selected.size() == 1)
095      */
096     @Override
097     protected void onDraw(Canvas canvas) {
098         /**
099          * 绘制连通路径,然后将路径以及两个图标清除
100          */
101         if(path != null && path.length >= 2){
102             for(int i = 0; i < path.length - 1;++i){
103                 Paint paint = new Paint();
104                 paint.setColor(Color.BLUE);
105                 paint.setStrokeWidth(3);
106                 paint.setStyle(Paint.Style.STROKE);
107                 Point p1 = indexToScreen(path[i].x,path[i].y);
108                 Point p2 = indexToScreen(path[i + 1].x,path[i + 1].y);
109                 canvas.drawLine(p1.x + iconSize/2, p1.y + iconSize/2,
110                         p2.x + iconSize/2, p2.y + iconSize/2, paint);
111             }
112             map[path[0].x][path[0].y] = 0;
113             map[path[path.length - 1].x][path[path.length -1].y] = 0;
114             selected.clear();  
115             path = null;
116         }
117         /**
118          * 绘制棋盘的所有图标 当这个坐标内的值大于0时绘制
119          */
120         for(int x = 1;x < xCount - 1; ++x){
121             for(int y = 1; y < yCount -1; ++y){
122                 if(map[x][y]>0){
123                     Point p = indexToScreen(x, y);
124                     canvas.drawBitmap(icons[map[x][y]], p.x,p.y,null);
125                 }
126             }
127         }
128         /**
129          * 绘制选中图标,当选中时图标放大显示
130          */
131         //for(Point position:selected){
132         if(selected.size() > 0){
133             Point position = selected.get(0);
134             Point p = indexToScreen(position.x, position.y);
135             if(map[position.x][position.y] >= 1){
136                 canvas.drawBitmap(icons[map[position.x][position.y]],
137                         null,
138                         new Rect(p.x-5, p.y-5, p.x + iconSize + 5, p.y + iconSize + 5), null);
139             }
140         }
141         super.onDraw(canvas);
142     }
143      
144     /**
145      * 工具方法
146      * @param x 数组中的横坐标
147      * @param y 数组中的纵坐标
148      * @return 将图标在数组中的坐标转成在屏幕上的真实坐标
149      */
150     public Point indexToScreen(int x,int y){
151         return new Point(x * iconSize,y * iconSize);
152     }
153     /**
154      * 工具方法
155      * @param x 屏幕中的横坐标
156      * @param y 屏幕中的纵坐标
157      * @return 将图标在屏幕中的坐标转成在数组上的虚拟坐标
158      */
159     public Point screenToIndex(int x,int y){
160         int xindex = x / iconSize;
161         int yindex = y / iconSize;
162         if(xindex < xCount && yindex < yCount){
163             return new Point(xindex,yindex);
164         }else{
165             return new Point(0,0);
166         }
167     }
168     /**
169      * 传进来path数据更新显示,也就是将能够连接的图标消除
170      * @param path
171      */
172     public void drawLine(Point[] path) {
173         this.path = path;
174         this.invalidate();
175     }
176      
177 }

此类当中,主要是实现了将连连看图标资源的载入并且使之与一个特定的int型key相绑定,所以在后面的对于图标的贴图,我们能够更加方便的操作。当然

此类中还需要存在一些必要的工具函数,比如说screenToIndex方法等,因为我们是自定义View在屏幕上绘图,需要用到屏幕坐标,但是同时,连连看游戏

中,我们还需要知道图标的索引(由于图标都是等长等宽,容易实现屏幕坐标与index索引之间的转换),以使方便操作。当然,此类中最重要的还是重写

的onDraw函数;此函数中首先判断path是否为null并且是否两个及以上的元素,我们之前定义path变量时,是将其作为保存连通路径的工具。(path中

的值也就是连通路径我们将在连接算法实现时中加入)这里我们首先在onDraw函数中绘制出线条(如果连通),随后将路径的首尾中的map值设为0,程

序中,第0行与最后一行map值始终为0,第0列与最后一列map值始终为0,map中的值0为0代表此处已经没有了图标,根据前面与图标资源的绑定值与

map中的值对应,map中的值为几则在相应的index上贴上相应的图标。在onDraw函数中,还有一个功能就是将选择的第一个图标放大,以提醒玩家。

最后绘制(贴图),如前面所说,map值为多少就在对应位置贴上相应的图标资源,有前面载入资源时可知并没有对应于0的图标资源,为0时即不贴图。

为了防止代码混乱,上面的BoardView 类并没有实现全部的功能,如touch事件的监听,连接算法的实现,判断是否无解等等。所以我们将BoardView

类进行扩展,继承BoardView的GameView(这样做也使代码不至于太混乱)。限于篇幅,我们可以先将GameView中用于监听剩余时间的内部类实现

(该类实现了Runnable接口):

01 /**
02  * 用于更新剩余时间的线程
03  * @author helloPe
04  *
05  */
06 class RefreshTime implements Runnable{
07  
08     @Override
09     public void run() {
10         if(isContinue){
11             while(leftTime > 0 && !isStop){
12                 timerListener.onTimer(leftTime);
13                 leftTime --;
14                 try {
15                     Thread.sleep(1000);
16                 } catch (InterruptedException e) {
17                     e.printStackTrace();
18                 }
19             }
20         }
21         if(isStop && leftTime > 0){
22             if(win())
23                 ;//setMode(WIN);
24             else
25             setMode(PAUSE);
26         }
27         //setMode(LOSE);
28         else if(leftTime == 0){
29             setMode(LOSE);
30         }
31     }
32 }
33 /**
34  * 停止显示的时间
35  */
36 public void stopTimer(){
37     isStop = true;
38     isContinue = false;
39 }
40 /**
41  * 设置继续
42  */
43 public void setContinue(){
44     isContinue = true;
45     isStop = false;
46     refreshTime = new RefreshTime();
47     Thread t = new Thread(refreshTime);    //注意正确启动一个实现Runnable接口的线程
48     t.start();
49 }

上面已经提过,此线程用于控制游戏的时间。

在此,再介绍自定义的几个接口,

1 public interface OnStateListener{
2     public void OnStateChanged(int StateMode);
3 }

只含有一个方法,主要对于游戏状态的变换的监听,比如pause,stop等等。

1 public interface OnTimerListener{
2     public void onTimer(int leftTime);
3 }

用于监听剩余时间,与上面线程不同的是,此方法中利用上面线程的leftTime的结果,主要用于更新游戏中用于提醒玩家的时间进度条。

1 public interface OnToolsChangeListener{
2     public void onRefreshChanged(int count);
3     public void onTipChanged(int count);
4 }

tool即是我们的游戏中提供给玩家的两个工具,一个是refresh一下游戏界面,即将现有的棋盘重新打乱(当然,现有图表数量不变),另一个是之前提过的hint的自动帮助功能,帮助玩家找到一组能够连通的图标。当然,这两种工具都有次数的限制。

  BoardView类及时间线程类的开发与介绍到此,后面我们将完整的实现游戏棋盘的绘制与touch事件的处理,以及游戏核心算法中连接算法、hint自动帮助算法与判断是否无解算法的实现。这些代码的处理都在继承自BoardView类的GameView类中。

  之所以写本系列的文章,为了记录android小项目的经历,增加实战的能力,做个总结。并不是为了做出多么新颖的项目,当然也是向不少的网友学习了的!

posted @ 2011-11-25 16:16  jeffkuang  阅读(277)  评论(0)    收藏  举报