利用websocket技术实现一个俄罗斯方块对战游戏

Posted by Yinode on Friday, December 15, 2017

TOC

预览

同步查看对手

技术栈

主要利用了websocket,由于ws天生是可以跨域的,所以我将静态页面部署在了gh-page上,而负责后端交互的部分我放在了我的VPS上。 socket.io 是一个优秀的ws调用库,可以同时运行在客户端与服务端。 简单的贴一下服务端的代码


let app = require("http").createServer();
let io = require("socket.io")(app);
let port = 3001;
let clientCount = 0;
let socketMap = {};

app.listen(port);

function bindEvent(socket, event) {
  socket.on(event, data => {
    if (socket.clientID % 2 === 0) {
      // 如果是第二个客户端发来的事件 就发送给第一个客户端
      if (socketMap[socket.clientID - 1]) {
        socketMap[socket.clientID - 1].emit(event, data);
      }
    } else {
      // 相反
      if (socketMap[socket.clientID + 1]) {
        socketMap[socket.clientID + 1].emit(event, data);
      }
    }
  });
}

io.on("connection", socket => {
  clientCount = clientCount + 1;
  socket.clientID = clientCount;
  socketMap[clientCount] = socket;
  if (clientCount % 2 === 1) {
    console.log("wating");
    socket.emit("waiting", "等待玩家进入");
  } else {
    console.log("start");
    socket.emit("start");
    if (socketMap[clientCount - 1]) {
      socketMap[clientCount - 1].emit("start");
    } else {
      //玩家1进入马上又离开 玩家2进入 玩家2显示离线
      socket.emit('leave')
    }
  }
  
  // 绑定会进行两次 同时建立两个不同的socket对象
  bindEvent(socket, "init");
  bindEvent(socket, "next");
  bindEvent(socket, "rotate");
  bindEvent(socket, "right");
  bindEvent(socket, "left");
  bindEvent(socket, "down");
  bindEvent(socket, "line");
  bindEvent(socket, "fall");
  bindEvent(socket, "addScope");
  bindEvent(socket, "lose");
  bindEvent(socket, "addLineSucces")

  socket.on("disconnect", () => {
    //确保删除不报错
    if (socket.clientID % 2 === 0) {
      if (socketMap[socket.clientID - 1]) {
        socketMap[socket.clientID - 1].emit("leave");
      }
    } else {
      if (socketMap[socket.clientID + 1]) {
        socketMap[socket.clientID + 1].emit("leave");
      }
    }
    delete socketMap[socket.clientID];
  });
});

console.log("ws Server listening to prot " + port);


客户端的原理

俄罗斯方块的本质就是一个二维数组到视图的一个隐射,整体方块槽与一个个方块其实都是一种二维数组。内部都是一些数组,比如0就对应着空,1可能就对应着绿色的一个实体小块。而方块的移动就对应了把小的二维数组插入到大的二维数组的内部,进行一个内部值的更新。当更新完毕我们就通知一些函数按照值来重新刷新视图的class。

要注意的事项

  • 在方块移动(比如正常下落,手动旋转,移动)之前要进行一次模拟碰撞检测,作出正确的应对
  • 不同颜色的方块应该在生成前加入一随机的值 比如3代表蓝色 那某个独立方块的内部值可能需要3+random number 这样才能做到不予其他蓝色方块穿透。
  • 方块的旋转我选择的是手动写这个方块的其他状态(四种二维数组提前写好)。不然旋转算法可能很复杂,没必要
  • 如何同时显示两个客户端(一个自己的一个对手的)依赖的就是两个game组件来启动,而remote的game通过远程发送的指令来控制。本地则是用户的操作来控制,每当本地操作产生就向远程发生一个事件来转发给另外一个客户端。
  • 在a成功消除一行后会给b增加一行干扰行,不然游戏可能很难结束。

最后,合理的抽象极其重要,这将是你减少代码量和复杂度最直接的方式。

总结

作为一项新技术,websocket很好的弥补了HTTP想要长连接必须使用长伦询的不便。

code preview