Android自定义View实现五子棋游戏
本文实例为大家分享了Android实现五子棋游戏的具体代码,供大家参考,具体内容如下
直接上效果图
原理
从棋盘到棋子,到开始下棋的各类点击事件,均在ChessView中实现,这个View没有提供自定义属性(因为我觉得没有必要~~~)。
项目GitHub地址:Wuziqi
实现步骤
1.新建一个棋子类,这个类非常简单,代码如下:
publicclassChess{
publicenumColor{BLACK,WHITE,NONE}
privateColorcolor;
publicChess(){
this.color=Color.NONE;
}
publicColorgetColor(){
returncolor;
}
publicvoidsetColor(Colorcolor){
this.color=color;
}
}
每个棋子类有三种状态,即WHITE,BLACK,NONE。这里我们使用枚举来表示这三种状态。
2.自定义ChessView类,这个类就是核心类了,我们这个五子棋的所有逻辑都是在这个类里面实现。构造方法初始化各个字段,代码如下:
publicChessView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
//初始化字段mEveryPlay,悔棋会用到
initEveryPlay();
//初始化每个棋子,设置属性为NONE
initChess();
//初始化棋盘画笔
initBoardPaint();
//初始化棋子画笔
initChessPaint();
//初始化背景画笔
initBgPaint();
}
各个方法的具体实现如下:
privatevoidinitEveryPlay(){
//初始化List大小,此方法不影响list.size()返回值
mEveryPlay=newArrayList<>(225);
}
privatevoidinitChess(){
mChessArray=newChess[15][15];
for(inti=0;i
3.重写onMeasure()方法,强制将View大小变为正方形,代码如下:
@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
intwidthSize=MeasureSpec.getSize(widthMeasureSpec);
intheightSize=MeasureSpec.getSize(heightMeasureSpec);
intmin=widthSize
之所以设置为16的整数倍而不是15,是因为如果设置成15,那么棋盘的背景就会跟棋盘最边界的线条重合,此时如果有棋子落在边界,棋子将不能显示完全。
4.重点来了,重写onDraw()方法,绘制出棋盘,代码如下:
@Override
protectedvoidonDraw(Canvascanvas){
intheight=getMeasuredHeight();
intwidth=getMeasuredWidth();
intavg=height/16;
canvas.drawRect(0,0,width,height,mBgPaint);
for(inti=1;i<16;i++){
//画竖线
canvas.drawLine(avg*i,avg,avg*i,height-avg,mBoardPaint);
//画横线
canvas.drawLine(avg,avg*i,width-avg,avg*i,mBoardPaint);
}
for(inti=1;i<16;i++){
for(intj=1;j<16;j++){
switch(mChessArray[i-1][j-1].getColor()){
caseBLACK:
mChessPaint.setColor(android.graphics.Color.BLACK);
break;
caseWHITE:
mChessPaint.setColor(android.graphics.Color.WHITE);
break;
caseNONE:
continue;
}
canvas.drawCircle(avg*i,avg*j,avg/2-0.5f,mChessPaint);
}
}
}
这样我们就将整个棋盘画出来了,之后我们只需要改变数组mChessArray[][]里面对象的Color属性,再调用invalidate()方法便可以刷新棋盘了。
5.接下来,我们便要处理点击事件,实现对弈的逻辑了,重写onTouchEvent()方法,代码如下:
@Override
publicbooleanonTouchEvent(MotionEventevent){
switch(event.getAction()){
caseMotionEvent.ACTION_DOWN:
//如果棋盘被锁定(即胜负已分,返回查看棋局的时候)
//此时只允许查看,不允许落子了
if(isLocked){
returntrue;
}
floatx=event.getX();
floaty=event.getY();
//以点击的位置为中心,新建一个小矩形
Rectrect=getLittleRect(x,y);
//获得上述矩形包含的棋盘上的点
Pointpoint=getContainPoint(rect);
if(point!=null){
//若点不为空,则刷新对应位置棋子的属性
setChessState(point);
//记录下每步操作,方便悔棋操作
mEveryPlay.add(point);
if(gameIsOver(point.x,point.y)){
//游戏结束弹窗提示
showDialog();
}
//更改游戏玩家
isBlackPlay=!isBlackPlay;
}
break;
caseMotionEvent.ACTION_MOVE:
break;
caseMotionEvent.ACTION_UP:
break;
}
returnsuper.onTouchEvent(event);
}
下面分别来说说调用到的各个方法的实现思路:
getLittleRect()
/**
*以传入点为中心,获得一个矩形
*
*@paramx传入点x坐标
*@paramy传入点y坐标
*@return所得矩形
*/
privateRectgetLittleRect(floatx,floaty){
intside=getMeasuredHeight()/16;
intleft=(int)(x-side/2);
inttop=(int)(y-side/2);
intright=(int)(x+side/2);
intbottom=(int)(y+side/2);
returnnewRect(left,top,right,bottom);
}
getContainPoint()
/**
*获取包含在rect中并且是能够下棋的位置的点
*
*@paramrect矩形
*@return返回包含的点,若没有包含任何点或者包含点已有棋子返回null
*/
privatePointgetContainPoint(Rectrect){
intavg=getMeasuredHeight()/16;
for(inti=1;i<16;i++){
for(intj=1;j<16;j++){
if(rect.contains(avg*i,avg*j)){
Pointpoint=newPoint(i-1,j-1);
//包含点没有棋子才返回point
if(mChessArray[point.x][point.y].getColor()==Chess.Color.NONE){
returnpoint;
}
break;
}
}
}
returnnull;
}
showDialog()
顺便一提,这个方法用的是v7包里面的对话框,因为这样可以在版本较低的安卓平台下也可以得到不错的显示效果,效果如下:
/**
*游戏结束,显示对话框
*/
privatevoidshowDialog(){
AlertDialog.Builderbuilder=newAlertDialog.Builder(getContext());
builder.setTitle("游戏结束");
if(isBlackPlay){
builder.setMessage("黑方获胜!!!");
}else{
builder.setMessage("白方获胜!!!");
}
builder.setCancelable(false);
builder.setPositiveButton("重新开始",newDialogInterface.OnClickListener(){
@Override
publicvoidonClick(DialogInterfacedialog,intwhich){
resetChessBoard();
dialog.dismiss();
}
});
builder.setNegativeButton("返回查看",newDialogInterface.OnClickListener(){
@Override
publicvoidonClick(DialogInterfacedialog,intwhich){
isLocked=true;
dialog.dismiss();
}
});
builder.show();
}
setChessState()
/**
*重新设定用户所点位置的棋子状态
*
*@parampoint棋子的位置
*/
privatevoidsetChessState(Pointpoint){
if(isBlackPlay){
mChessArray[point.x][point.y].setColor(Chess.Color.BLACK);
}else{
mChessArray[point.x][point.y].setColor(Chess.Color.WHITE);
}
invalidate();
}
以上几个方法都较为简单不多说了,接下来重点讲一下判断游戏结束的逻辑。
-gameIsOver()
/**
*判断游戏是否结束,游戏结束标志:当前落子位置与其他同色棋子连成5个
*
*@paramx落子位置x坐标
*@paramy落子位置y坐标
*@return若连成5个,游戏结束,返回true,负责返回false
*/
privatebooleangameIsOver(intx,inty){
Chess.Colorcolor=mChessArray[x][y].getColor();
returnisOverA(x,y,color)||isOverB(x,y,color)||isOverC(x,y,color)||isOverD(x,y,color);
}
这个方法用来判断游戏是否结束,思路便是以当前落子位置为基准,去寻找竖直、水平、左上至右下、左下至右上四个方向是否连成5子,分别对应isOverA(),isOverB(),isOverC(),isOverD()四个方法,这四个方法的实现如下:
privatebooleanisOverA(intx,inty,Chess.Colorcolor){
intamount=0;
for(inti=y;i>=0;i--){
if(mChessArray[x][i].getColor()==color){
amount++;
}else{
break;
}
}
for(inti=y;i5;
}
privatebooleanisOverB(intx,inty,Chess.Colorcolor){
intamount=0;
for(inti=x;i>=0;i--){
if(mChessArray[i][y].getColor()==color){
amount++;
}else{
break;
}
}
for(inti=x;i5;
}
privatebooleanisOverC(intx,inty,Chess.Colorcolor){
intamount=0;
for(inti=x,j=y;i>=0&&j>=0;i--,j--){
if(mChessArray[i][j].getColor()==color){
amount++;
}else{
break;
}
}
for(inti=x,j=y;i5;
}
privatebooleanisOverD(intx,inty,Chess.Colorcolor){
intamount=0;
for(inti=x,j=y;i=0;i++,j--){
if(mChessArray[i][j].getColor()==color){
amount++;
}else{
break;
}
}
for(inti=x,j=y;i>=0&&j5;
}
6.最后定义两个公有方法,方便Activity调用,用来执行悔棋和重置棋盘操作。两个方法代码如下:
/**
*悔棋,实现思路为:记录每一步走棋的坐标,若点击了悔棋,
*则拿出最后记录的坐标,对mChessArray里面对应坐标的
*棋子进行处理(设置颜色为NONE),并移除集合里面最后
*一个元素
*/
publicvoidretract(){
if(mEveryPlay.isEmpty()){
return;
}
Pointpoint=mEveryPlay.get(mEveryPlay.size()-1);
mChessArray[point.x][point.y].setColor(Chess.Color.NONE);
mEveryPlay.remove(mEveryPlay.size()-1);
isLocked=false;
isBlackPlay=!isBlackPlay;
invalidate();
}
/**
*重置棋盘
*/
publicvoidresetChessBoard(){
for(Chess[]chessRow:mChessArray){
for(Chesschess:chessRow){
chess.setColor(Chess.Color.NONE);
}
}
mEveryPlay.clear();
isBlackPlay=true;
isLocked=false;
invalidate();
}
到此,ChessView已经写完了,接下来只要在布局文件里面声明即可。
7.在activity_main布局文件如下,非常简单,我相信不用多说都能看懂:
8.最后一步了,只需要在MainActivity里面拿到ChessView对象和两个Button按钮,即可实现悔棋与重新开始:
packagecom.yangqi.wuziqi;
importandroid.os.Bundle;
importandroid.support.v7.app.AppCompatActivity;
importandroid.view.View;
importandroid.widget.Button;
publicclassMainActivityextendsAppCompatActivity{
privateButtonbt_reset;
privateButtonbt_retract;
privateChessViewchessView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
initListener();
}
privatevoidinitListener(){
bt_reset.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
chessView.resetChessBoard();
}
});
bt_retract.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
chessView.retract();
}
});
}
privatevoidinitUI(){
bt_reset=(Button)findViewById(R.id.bt_reset);
bt_retract=(Button)findViewById(R.id.bt_retract);
chessView=(ChessView)findViewById(R.id.chessView);
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。