Android:实现井字棋小游戏

 

井字棋实现方式有许多,最简单的方法是将9个imagView组成棋盘,然后通过一些逻辑设计进行游戏。本文采用的是自定义View的方式进行游戏设计,通过继承View进行棋盘,选中状态绘制,效果如下:

 

1.棋盘绘制

  通过选取设置宽高中最小值作为控件宽高,并平均分为3段作为每小格的长度,在onDraw()方法中绘制棋盘,代码如下:

//重写onMeasure()设置宽高
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int size = Math.min(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec));
        setMeasuredDimension(size,size);
    }


//棋盘画笔
private Paint mPaint;

@Override
    protected void onDraw(Canvas canvas) {
        //每小格长度
        length = getWidth()/3;
        //棋盘绘制
        for(int i = 0;i < 4;i++){
            canvas.drawLine(length*i,0,length*i,3*length,mPaint);
            canvas.drawLine(0,length*i,length*3,length*i,mPaint);
        }
}

 

 2.选中状态绘制

  为了区分选中操作的玩家,新建枚举类型:

//代表不同玩家(NONE用来表示平局情况获胜玩家)
    public enum Player{
        USER_ONE,USER_TWO,NONE
    }

 当玩家点击屏幕时,我们需要知道他所选的格子是哪一个,通过实现 View.OnTouchListener 接口进行监听,代码如下(其中的判断是避免滑动情况):

 // down 事件 坐标
    private float lastX;
    private float lastY;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_UP:
                // UP 事件坐标 与 Down事件坐标 距离不能太远
                if(Math.abs(lastX-x)

 其中calculateTouchItem方法功能为计算选中格子,代码如下:

//被选格子(玩家选择前先通过此数组判断是否可选,选择后将对应格子值设为非0)
private int[] locations = new int[9];
//每个格子对应字母编号,将通过字母组合判断是否获胜
private String[] items = new String[9];
//玩家所选格子对应字母集合
private List user1Selected = new ArrayList<>();
private List user2Selected = new ArrayList<>();
//已选格子信息集合(格子所选玩家,格子中心点坐标)
private List mList = new ArrayList<>();

//当前回合玩家
private Player currentPlayer;

private void calculateTouchItem(float x, float y) {
        //判断所在行列
        int j = (int)x/length;
        int i = (int)y/length;
        //判断是否在棋盘内
        if(i < 3 && j < 3){
            //判断是否可选
            if(locations[i*3+j] == 0){
                //创建选中格子信息并添加到选中格子list中
                ItemInformation itemInformation = new ItemInformation(items[i*3+j],currentPlayer,i,j);
                mList.add(itemInformation);
                //将格子设为已选状态
                locations[i*3+j] = 1;
                //将选中格子字母编号添加到当前玩家选中格子list中
                if(currentPlayer == Player.USER_ONE){
                    user1Selected.add(items[i*3+j]);
                }else {
                    user2Selected.add(items[i*3+j]);
                }
                //重绘
                invalidate();
            }
        }

    }

其中ItemInformation保存选中格子相关信息,具体如下:

//存储选中格子信息
    private class ItemInformation{

        private int i;
        private int j;
        //选中格子字母编号
        private String location;
        private Player player;

        public ItemInformation(String location, Player player,int i,int j) {
            this.location = location;
            this.player = player;
            this.i = i;
            this.j = j;
        }

        public String getLocation() {
            return location;
        }

        public Player getPlayer() {
            return player;
        }

        public int getI() {
            return i;
        }

        public int getJ() {
            return j;
        }
    }

 九个格子字母编号和数字编号如下:

 A(0) B(1) C(2)
D(3) E(4) F(5)
G(6) H(7) I(8)

计算好选中格子后调用invalidate()方法进行重绘,修改onDraw()方法,代码如下:

//是否第一次加载
private boolean isFirst = true;
 //玩家图案对应颜色
private int userColorOne;
private int userColorTwo;

@Override
    protected void onDraw(Canvas canvas) {
        length = Math.min(getWidth(),getHeight())/3;
        //棋盘绘制
        for(int i = 0;i < 4;i++){
            canvas.drawLine(length*i,0,length*i,3*length,mPaint);
            canvas.drawLine(0,length*i,length*3,length*i,mPaint);
        }
        //选中格子绘制
        if(!isFirst){
            for(int i = 0;i < mList.size();i++){
                switch (mList.get(i).getPlayer()){
                    case USER_ONE:
                        userPaint.setColor(userColorOne);
                        drawUserSelected(canvas,mList.get(i));
                        break;
                    case USER_TWO:
                        userPaint.setColor(userColorTwo);
                        drawUserSelected(canvas,mList.get(i));
                        break;
                }
            }
            //查看游戏状态是否结束
            checkStatus();

        }
        if(isFirst){
            isFirst = false;
        }
    }

 遍历所有选中格子,通过对应ItemInformation信息知道所选玩家,设置对应颜色,再调用drawUserSelected()方法绘制,具体代码如下:

//玩家选择图案画笔
private Paint userPaint; 

//绘制选中格子
    private void drawUserSelected(Canvas canvas, ItemInformation itemInformation) {
        //计算格子中心坐标
        int centerX = itemInformation.getJ() * length + length/2;
        int centerY = itemInformation.getI() * length + length/2;
        userPaint.setStrokeWidth(5f);
        //玩家一 绘制 × 图案
        if(itemInformation.getPlayer() == Player.USER_ONE){
            float delta = (float) Math.sqrt(0.08*length*length);
            canvas.drawLine(centerX-delta,centerY-delta,centerX+delta,centerY+delta,userPaint);
            canvas.drawLine(centerX+delta,centerY-delta,centerX-delta,centerY+delta,userPaint);
        }else {
            //玩家二 绘制 ○ 图案
            float radius =  0.4f * length;
            canvas.drawCircle(centerX,centerY,radius,userPaint);
        }
    }

 绘制后就调用checkStatus()方法进行判断,是否有玩家获胜,是否还有未选格子,如游戏还能继续则切换玩家。具体代码如下:

//查看游戏状态
    private void checkStatus(){
        // 避免非用户点击情况下 onDraw()方法调用 造成 当前玩家的切换
        if(mList.size() == 0 || mList.size()==lastCount) return;
        lastCount = mList.size();
        //查看是否有人获胜
        boolean isSuccess = checkIsSuccessful();
        if(isSuccess){
            if(mListener != null){
                mListener.onSuccess(currentPlayer);
            }
            setOnTouchListener(null);
        }else {
            //判断是否平局
            if(mList.size() == 9){
                if(mListener != null){
                    mListener.onSuccess(Player.NONE);
                }
                return;
            }
            //切换当前用户
            switch (currentPlayer){
                case USER_TWO:
                    currentPlayer = Player.USER_ONE;
                    break;
                case USER_ONE:
                    currentPlayer = Player.USER_TWO;
                    break;
            }
        }
        Log.e("Chess:",currentPlayer+"");
    }

  其中checkIsSuccessful()方法判断有人获胜,具体代码如下:

private boolean checkIsSuccessful() {
        boolean isSuccess = false;
        String tmp = "";
        if(currentPlayer == Player.USER_ONE){
            if(user1Selected.size() >= 3){
                //将玩家所选格子字母编号排序
                Collections.sort(user1Selected);
                //回溯法判断是否获胜
                searchResult(user1Selected,tmp,0);
                isSuccess = result.size() > 0;
            }
        }else {
            if(user2Selected.size() >= 3){
                Collections.sort(user2Selected);
                searchResult(user2Selected,tmp,0);
                isSuccess = result.size() > 0;
            }
        }
        return isSuccess;
    }

 通过调用searchResult()方法进行结果查找,具体代码如下:

//获胜的所有格子字母组合(按字典序排序)
private List successResult = new ArrayList<>();
//存储玩家获胜的字母组合
private List result = new ArrayList<>();


 //回溯法 将所有情况进行判断
    private void searchResult(List userSelected,String tmp,int index) {
        if(tmp.length() == 3){
            System.out.println(tmp);
            if(successResult.contains(tmp)){
                result.add(tmp);
            }
            return;
        }
        for(int i = index;i < userSelected.size();i++){
            tmp += userSelected.get(i);
            searchResult(userSelected,tmp,i+1);
            tmp = tmp.substring(0,tmp.length()-1);
        }

    }

 其中successResult存储了所有获胜情况下的字母组合(按字典序),在checkIsSuccessful()方法中先调用Collections.sort()将玩家所选格子字母编号进行排序,在通过回溯法将所有3个字母组合与successResult结果进行比较,如果存在即获胜,将结果加入result中,在checkIsSuccessful()方法中可通过result长度可知是否获胜。如果有人获胜则回调游戏结束接口,接口如下:

//游戏结束监听器
    private OnSuccessListener mListener;

//游戏结束回调接口
    public interface OnSuccessListener{
        public void onSuccess(Player player);
    }

//设置游戏结束回调接口
    public void setOnSuccessListener(OnSuccessListener listener){
        mListener = listener;
    }

  回调代码在checkStatus()方法,如下:

 //查看游戏状态
    private void checkStatus(){
        // 避免非用户点击情况下 onDraw()方法调用 造成 当前玩家的切换
        if(mList.size() == 0 || mList.size()==lastCount) return;
        lastCount = mList.size();
        //查看是否有人获胜
        boolean isSuccess = checkIsSuccessful();
        if(isSuccess){
            if(mListener != null){
                mListener.onSuccess(currentPlayer);
            }
            setOnTouchListener(null);
        }else {
            //判断是否平局
            if(mList.size() == 9){
                if(mListener != null){
                    mListener.onSuccess(Player.NONE);
                }
                return;
            }
            //切换当前用户
            switch (currentPlayer){
                case USER_TWO:
                    currentPlayer = Player.USER_ONE;
                    break;
                case USER_ONE:
                    currentPlayer = Player.USER_TWO;
                    break;
            }
        }
        Log.e("Chess:",currentPlayer+"");
    }

到此我们的自定义view也完成了,上面代码中的变量在构造函数中,进行初始化,其中部分属性可设为自定义属性供用户设置,自定义属性及变量初始化如下:


        
        
        
    
    public ChessView(Context context) {
        this(context,null);
    }

    public ChessView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ChessView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs);
    }


    private void init(Context context,AttributeSet attributeSet) {
        initData();
        TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ChessView);
        lineColor = typedArray.getColor(R.styleable.ChessView_lineColor,Color.BLACK);
        userColorOne = typedArray.getColor(R.styleable.ChessView_user_color_one,Color.BLACK);
        userColorTwo = typedArray.getColor(R.styleable.ChessView_user_color_two,Color.RED);
        typedArray.recycle();

        userPaint = new Paint();
        userPaint.setStyle(Paint.Style.STROKE);
        userPaint.setAntiAlias(true);

        mPaint = new Paint();
        mPaint.setColor(lineColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);

        currentPlayer = Player.USER_ONE;
    }

    private void initData(){
        items[0] = "A";
        items[1] = "B";
        items[2] = "C";
        items[3] = "D";
        items[4] = "E";
        items[5] = "F";
        items[6] = "G";
        items[7] = "H";
        items[8] = "I";
        successResult.add("ABC");
        successResult.add("DEF");
        successResult.add("GHI");
        successResult.add("ADG");
        successResult.add("BEH");
        successResult.add("CFI");
        successResult.add("AEI");
        successResult.add("CEG");
    }

  我们可在布局中使用此自定义view并实现游戏结束接口,具体就不介绍了,代码都有注释,布局代码和activity代码如下:




    
    
public class ChessActivity extends AppCompatActivity implements View.OnClickListener {

    private ChessView chessView;
    private Button chessButton;

    //游戏结束信息提醒
    private AlertDialog.Builder alertDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chess);
        initDialog();
        //判断是否为第一次进入游戏 是的话需点击开始游戏,否则直接进行游戏
        Intent intent = getIntent();
        chessButton = findViewById(R.id.chess_button);
        chessView = findViewById(R.id.chess_view);

        if(intent.getIntExtra("re",0) == 1){
            chessButton.setVisibility(View.INVISIBLE);
            //使棋盘接收点击事件
            chessView.setOnTouchListener();
        }else {
            chessButton.setText("开始游戏");
            chessButton.setOnClickListener(this);
        }


        chessView.setOnSuccessListener(new ChessView.OnSuccessListener() {
            @Override
            public void onSuccess(ChessView.Player player) {
                //根据回调结果显示结束信息
                switch (player){
                    case NONE:
                        alertDialog.setMessage("平局!");  //设置提示信息
                        break;
                    case USER_ONE:
                        alertDialog.setMessage("×方获胜!");  //设置提示信息
                        break;
                    case USER_TWO:
                        alertDialog.setMessage("○方获胜!");  //设置提示信息
                        break;
                }
                alertDialog.show(); //显示
            }
        });
    }

    //初始化Dialog
    private void initDialog(){
        alertDialog = new AlertDialog.Builder(this);
        alertDialog.setTitle("游戏信息");  //设置标题
        alertDialog.setCancelable(false);   //是否点击屏幕可取消
        //确定按钮点击事件
        alertDialog.setPositiveButton("再来一局", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent intent = new Intent(ChessActivity.this,ChessActivity.class);
                intent.putExtra("re",1);
                startActivity(intent);
                finish();
            }
        });
        //取消按钮点击事件
        alertDialog.setNegativeButton("返回", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                chessButton.setVisibility(View.VISIBLE);
                chessButton.setText("再来一局");
                chessButton.setOnClickListener(ChessActivity.this);
            }
        });
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.chess_button) {
            if (chessButton.getText().toString().equals("开始游戏")) {
                Log.e("ChessActivity:","开始游戏");
                chessView.setOnTouchListener();
            } else {
                Intent intent = new Intent(ChessActivity.this, ChessActivity.class);
                intent.putExtra("re", 1);
                startActivity(intent);
                finish();
            }
        }
    }
}

 代码已上传GitHub,地址 : https://github.com/YangRT/Chess

你可能感兴趣的:(Android,自定义View)