带有Node.js和socketio的实时两人游戏的服务器架构

时间:2019-02-14 01:50:40

标签: node.js socket.io architecture

我对WebSockets和socket.io有点陌生,我正在制作一个两人蛇游戏,其中一个人创建游戏,另一个人加入。

游戏状态将由服务器管理,客户端将每100毫秒接收更新的状态以进行渲染。

现在,在传统的基于restapi的应用程序中,我们拥有MVC架构,其中的所有内容都分离为模型,视图和路线。可以单独测试。

我想知道如何在基于socket.io的应用程序上工作。构造应用程序的好方法是什么?

这是我想出的一个天真的实现,

文件夹结构:

- package.json
- server.js
- Game.js

package.json

{
  "name": "rattle-battle-backend",
  "version": "1.0.0",
  "main": "server.js",
  "license": "MIT",
  "dependencies": {
    "express": "^4.16.4",
    "socket.io": "^2.2.0"
  }
}

server.js

const express = require('express');
const socket = require('socket.io');
const PORT = process.env.PORT || 3000;

const app = express();
const server = app.listen(PORT);

const Game = require('./Game');

const currentlyOnlineGames = {};

const io = socket(server);

io.sockets.on('connection', socket => {
  socket.on('create_game', () => {
    if (socket.current_room) {
      socket.emit('fail', 'you have already created a room');
    } else {
      const game = new Game(socket.id, io);
      currentlyOnlineGames[game.roomId] = game;
      socket.join(game.roomId);
      socket.current_room = game.roomId;
      socket.emit('game_created', game);
    }
  });

  socket.on('join_game', roomId => {
    const game = games_running[roomId];
    if (!game) {
      socket.emit('fail', 'game was not found');
    } else if (socket.id === game.snake1.id) {
      socket.emit('fail', 'you cannot join your own game');
    } else {
      socket.join(roomId);
      socket.current_room = roomId;
      game.joinGame(socket.id);
      game.start(currentlyOnlineGames);
    }
  });

  socket.on('change_snake_direction', ({ roomId, direction, id }) => {
    const game = currentlyOnlineGames[roomId];
    game.updateDirection(direction, id);
  });
});

Game.js

class Game {
  constructor(snake1Id, io) {
    this.roomId = (Math.random() * 100000000000).toFixed(0);

    this.snake1 = {
      id: snake1Id,
      positions: [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 2, y: 0 }],
      color: 'green',
      dir: {
        x: 1,
        y: 0
      }
    };

    this.freeToJoin = true;

    this.food = this.makeFood();
    this.interval = null;
    this.winner = null;
    this.ended = false;
    this.io = io;
  }

  joinGame(snake2Id) {
    this.snake2 = {
      id: snake2Id,
      positions: [{ x: 37, y: 39 }, { x: 38, y: 39 }, { x: 39, y: 39 }],
      color: 'red',
      dir: {
        x: -1,
        y: 0
      }
    };

    this.freeToJoin = false;
  }

  start(currentlyOnlineGames) {
    this.io.to(roomId).emit('game started', game);

    this.interval = setInterval(() => {
      if (!this.ended) {
        this.moveSnakes();
        this.io.to(this.roomId).emit('snakes_moved', game);
      } else {
        clearInterval(this.interval);
        delete currentlyOnlineGames[this.roomId];
        this.io.to(game.roomId).emit('game_ended', game.winner);
      }
    }, 100);
  }

  makeFood() {
    const x = Math.floor(Math.random() * 40);
    const y = Math.floor(Math.random() * 40);
    return {
      x,
      y
    };
  }

  moveSnakes() {
    [this.snake1, this.snake2].forEach(snake => {
      const [head] = snake.positions;
      const { x, y } = head;

      const newHead = {
        x: x + snake.dir.x,
        y: y + snake.dir.y
      };

      const otherSnake = snake === this.snake1 ? this.snake2 : this.snake1;
      const collides = otherSnake.positions.find(({ x, y }) => {
        return x === newHead.x && y === newHead.y;
      });

      if (newHead.x > 39 || newHead.y > 39 || newHead.x < 0 || newHead.y < 0) {
        const winner = this.snake1 === snake ? this.snake2.id : this.snake1.id;
        this.winner = winner;
        this.ended = true;
      } else if (collides) {
        const winner = otherSnake.id;
        this.winner = winner;
        this.ended = true;
      } else {
        snake.positions.pop();
        snake.positions.unshift(newHead);
        this.checkFoodEaten();
      }
    });
  }

  checkFoodEaten() {
    [this.snake1, this.snake2].forEach(snake => {
      const [head] = snake.positions;

      if (this.food.x === head.x && this.food.y === head.y) {
        snake.positions.push({ ...this.food });
        this.food = this.makeFood();
      }
    });
  }

  updateDirection(direction, id) {
    const snakeToMove = this.snake1.id === id ? this.snake1 : this.snake2;

    switch (direction) {
      case 'Right':
        snakeToMove.dir.x = 1;
        snakeToMove.dir.y = 0;
        break;
      case 'Left':
        snakeToMove.dir.x = -1;
        snakeToMove.dir.y = 0;
        break;
      case 'Up':
        snakeToMove.dir.x = 0;
        snakeToMove.dir.y = -1;
        break;
      case 'Down':
        snakeToMove.dir.x = 0;
        snakeToMove.dir.y = 1;
        break;
      default:
        break;
    }
  }
}

module.exports = Game;

0 个答案:

没有答案