• 腾讯前端面试题:一言不合就写个五子棋


    近日接到腾讯 CDC 前端开发团队的求职意向询问,在微信上简单地聊了下技术,然后抛给我一道面试题。题目内容是编写一个单机五子棋,用原生 web 技术实现,兼容 Chrome 即可,完成时间不作限制。同时还有几个要求:

    1. 实现胜负判断,并给出赢棋提示。任意一方赢棋,锁定棋盘。
    2. 尽可能考虑游戏的扩展性,界面可用 DOM/ Canvas 实现,并且切换实现方式代价最小。
    3. 实现悔棋和撤销悔棋功能。
    4. 人机对战部分可选。

    工作上一直跟游戏开发毫无关联,自己也不怎么热衷玩游戏,不过五子棋还是玩过的。简单构思了下,决定用 DOM 实现。当天晚上在家忙活了两个多小时,基本完成。最终效果图如下:

    简易五子棋

    在线 Demo

    因为代码规模较小,总共不到 250 行,就没有考虑分文件模块化的设计,一个 game.js文件搞定。主要定义了 三个类:Board, Piece 和 Game,分别代表棋盘、棋子和整个游戏。

    游戏状态数据

    用一个二维数组保存棋盘数据,data[x][y] = 0表示该位置为空,data[x][y] = 1表示放置了黑子,data[x][y] = 2表示放置了白子。

    下棋动作

    监听棋盘的点击事件,计算出点位。玩家可能不是精确地点击交叉点,所以要进行纠偏计算。黑白交替进行,如果点位合法,就创建一个 DOM 元素表示棋子。

    悔棋与撤销悔棋

    每下一步棋,都保存当前棋子的坐标和 DOM 元素引用。如果要悔棋,就把该位置的数据清零,同时把 DOM 移除掉。撤销悔棋则执行相反的操作。

    胜负判定

    按照简单的规则,从当前下子点位的八个方向判断。如果有一个方向满足连续5个黑子或白子,游戏结束。

    全局变量

    var SIZE = 15;
    var BLACK = 1;
    var WHITE = 2;
    var WIN = 5;
    
    function approximate(number) {
        if(number - Math.floor(number) > 0.5) {
            return Math.ceil(number);
        }
        return Math.floor(number);
    }
    
    

    Board 类

    //棋盘
    function Board(el) {
        this.el = typeof el === 'string' ? document.querySelector(el) : el;
    }
    
    //初始化棋盘
    Board.prototype.init = function() {
        this.el.innerHTML = '';
        var frag = document.createDocumentFragment();
        for (var i = SIZE - 1; i >= 0; i--) {
            var row = document.createElement('div');
            row.classList.add('row');
            for (var j = SIZE - 1; j >= 0; j--) {
                var cell = document.createElement('div');
                cell.classList.add('cell');
                row.appendChild(cell);
            }
            frag.appendChild(row);
        }
        this.el.appendChild(frag);
        var aCell = this.el.querySelector('.cell');
        var rect = aCell.getBoundingClientRect();
        var maxWidth = Math.min(document.body.clientWidth * 0.8, SIZE * 40);
        var w = ~~(maxWidth / (SIZE - 1));
        this.el.style.height = w * (SIZE - 1) + 'px';
        this.el.style.width = w * (SIZE - 1) + 'px';
        rect = aCell.getBoundingClientRect();
        this.unit = rect.width;
    }
    
    //画棋子
    Board.prototype.drawPiece = function(piece) {
        var dom = document.createElement('div');
        dom.classList.add('piece');
        dom.style.width = this.unit + 'px';
        dom.style.height = this.unit + 'px';
        dom.style.left = ~~((piece.x - .5) * this.unit) + 'px';
        dom.style.top = ~~((piece.y - .5) * this.unit) + 'px';
        dom.classList.add(piece.player === 1 ? 'black' : 'white');
        this.el.appendChild(dom);
        return dom;
    }
    

    Piece 类

    //棋子
    function Piece(x, y, player) {
        this.x = x;
        this.y = y;
        this.player = player;
    }
    

    Game 类

    function Game(engine) {
        this.engine = engine || 'DOM';
        this.init();
    }
    
    Game.prototype.init = function() {
        this.ended = false;
        var chessData = new Array(SIZE);
        for (var x = 0; x < SIZE; x++) {
            chessData[x] = new Array(SIZE);
            for (var y = 0; y < SIZE; y++) {
                chessData[x][y] = 0;
            }
        }
        this.data = chessData;
        this.currentPlayer = WHITE;
        this.updateIndicator();
    }
    
    Game.prototype.start = function() {
        var board = new Board('.board');
        board.init();
        this.board = board;
    
        var rect = this.board.el.getBoundingClientRect();
        this.board.el.addEventListener('click', function(event) {
            var ptX = event.clientX - rect.left;
            var ptY = event.clientY - rect.top;
            var x = approximate(ptX / this.board.unit);
            var y = approximate(ptY / this.board.unit);
            console.log(x, y);
            this.play(x, y);
        }.bind(this));
    
        var btnUndo = document.querySelector('.undo');
        var btnRedo = document.querySelector('.redo');
        var btnRestart = document.querySelector('.restart');
        btnUndo.addEventListener('click', function() {
            this.undo();
        }.bind(this));
    
        btnRedo.addEventListener('click', function() {
            this.redo();
        }.bind(this));
    
        btnRestart.addEventListener('click', function() {
            this.init();
            this.board.init();
        }.bind(this));
    }
    
    Game.prototype.play = function(x, y) {
        if (this.ended) {
            return;
        }
        if (this.data[x][y] > 0) {
            return;
        }
        if(!this.lockPlayer) {
            this.currentPlayer = this.currentPlayer === BLACK ? WHITE : BLACK;
        }
        this.lockPlayer = false;
        var piece = new Piece(x, y, this.currentPlayer);
        var pieceEl = this.board.drawPiece(piece);
        this.data[x][y] = this.currentPlayer;
        this.updateIndicator();
        var winner = this.judge(x, y, this.currentPlayer);
        this.ended = winner > 0;
        if(this.ended) {
            setTimeout(function() {
                this.gameOver();
            }.bind(this), 0);
        }
        this.move = {
            piece: piece,
            el: pieceEl
        };
    }
    
    Game.prototype.updateIndicator = function() {
        var el = document.querySelector('.turn');
        if(this.currentPlayer === WHITE) {
            el.classList.add('black');
            el.classList.remove('white');
        } else {
            el.classList.add('white');
            el.classList.remove('black');
        }
    }
    
    Game.prototype.gameOver = function() {
        alert((this.currentPlayer === BLACK ? '黑方' : '白方') + '胜!');
    }
    
    Game.prototype.undo = function() {
        if(this.ended) {
            return;
        }
        this.lockPlayer = true;
        this.move.el.remove();
        var piece = this.move.piece;
        this.data[piece.x][piece.y] = 0;
    }
    
    Game.prototype.redo = function() {
        if(this.ended) {
            return;
        }
        this.lockPlayer = true;
        this.board.el.appendChild(this.move.el);
        var piece = this.move.piece;
        this.data[piece.x][piece.y] = piece.player;
    }
    
    //判断胜负
    Game.prototype.judge = function(x, y, player) {
        var horizontal = 0;
        var vertical = 0;
        var cross1 = 0;
        var cross2 = 0;
    
        var gameData = this.data;
        //左右判断 
        for (var i = x; i >= 0; i--) {
            if (gameData[i][y] != player) {
                break;
            }
            horizontal++;
        }
        for (var i = x + 1; i < SIZE; i++) {
            if (gameData[i][y] != player) {
                break;
            }
            horizontal++;
        }
        //上下判断 
        for (var i = y; i >= 0; i--) {
            if (gameData[x][i] != player) {
                break;
            }
            vertical++;
        }
        for (var i = y + 1; i < SIZE; i++) {
            if (gameData[x][i] != player) {
                break;
            }
            vertical++;
        }
        //左上右下判断 
        for (var i = x, j = y; i >= 0, j >= 0; i--, j--) {
            if (gameData[i][j] != player) {
                break;
            }
            cross1++;
        }
        for (var i = x + 1, j = y + 1; i < SIZE, j < SIZE; i++, j++) {
            if (gameData[i][j] != player) {
                break;
            }
            cross1++;
        }
        //右上左下判断 
        for (var i = x, j = y; i >= 0, j < SIZE; i--, j++) {
            if (gameData[i][j] != player) {
                break;
            }
            cross2++;
        }
        for (var i = x + 1, j = y - 1; i < SIZE, j >= 0; i++, j--) {
            if (gameData[i][j] != player) {
                break;
            }
            cross2++;
        }
        if (horizontal >= WIN || vertical >= WIN || cross1 >= WIN || cross2 >= WIN) {
            return player;
        }
        return 0;
    }
    

    启动游戏

    document.addEventListener('DOMContentLoaded', function() {
        var game = new Game();
        game.start();
        console.log('DOMContentLoaded')
    })
    

    总结:整体还是比较简单的,游戏逻辑已经抽象出来,界面部分可替换成 Canvas 实现。人机对战部分没有实现,没去研究五子棋赢棋策略。由于没花太多时间,代码比较粗糙,界面也比较丑。如果大家有更好的实现方式,欢迎交流。

    后记
    多年前也折腾过一些小游戏,比如:
    7X7小游戏
    用Vue.js和Webpack开发Web在线钢琴
    止增笑耳。

  • 相关阅读:
    Sqlserver 批量数据更改
    mysql not in、left join、IS NULL、NOT EXISTS 效率问题记录
    SQLServer 与 MySQL
    MySQL 行号(类似SQLServer的row_number())
    c# 字符串排序 (面试题)
    c# 多线程里面创建byte数组发生内存溢出异常求解
    c# 遇到的问题,求解?
    solr-4.10.3.tgz.tgz下载
    VMware虚拟机克隆或复制linux后无法上网的解决方案
    通配符的匹配很全面, 但无法找到元素 'dubbo:application' 的声明。
  • 原文地址:https://www.cnblogs.com/lzkwin/p/6933942.html
Copyright © 2020-2023  润新知