Merge branch 'second' of http://git.kingsome.cn/node/card_svr into second

This commit is contained in:
yuexin 2021-01-15 11:05:24 +08:00
commit 14f3cc5628
7 changed files with 608 additions and 173 deletions

2
src/global.d.ts vendored
View File

@ -30,6 +30,8 @@ declare module "colyseus" {
battleMan: BattleHandler;
dispatcher: Dispatcher;
mainClock: Delayed;
robotCount: number;
match: boolean;
/**
* sessionId获取client
* @param player id或者玩家的对象

View File

@ -1,15 +1,14 @@
import http from "http";
import express from "express";
import cors from "cors";
import {RedisPresence, Server} from "colyseus";
import {Server} from "colyseus";
import {monitor} from "@colyseus/monitor";
import rateLimit from "express-rate-limit";
// import socialRoutes from "@colyseus/social/express"
import {GeneralRoom} from "./rooms/GeneralRoom";
import {MongooseDriver} from "colyseus/lib/matchmaker/drivers/MongooseDriver";
import {initData} from "./common/GConfig";
import {Config} from "./cfg/Config";
import {RankedLobbyRoom} from "./rooms/RankedLobbyRoom";
require('./rooms/MSender');
require('./rooms/RoomExtMethod');
@ -34,7 +33,9 @@ const gameServer = new Server({
// register your room handlers
gameServer.define('general_room', GeneralRoom);
gameServer
.define('match_room', RankedLobbyRoom)
.filterBy(['numClientsToMatch']);
/**
* Register @colyseus/social routes
*
@ -61,5 +62,6 @@ gameServer.onShutdown(function () {
console.log("master process is being shut down!");
//TODO:: 保存所有数据至db, 重启时恢复
});
gameServer.listen(port).then(()=>{});
gameServer.listen(port).then(() => {
});
console.log(`Listening on ws://localhost:${port}`)

View File

@ -30,6 +30,8 @@ export class GeneralRoom extends Room {
gameClock: Map<string, Delayed> = new Map();
assistMap: Map<String, RobotClient> = new Map();
match = false;
robotCount = 0;
async onAuth (client:Client, options: any, request: IncomingMessage) {
debugRoom(options);
@ -40,8 +42,14 @@ export class GeneralRoom extends Room {
}
onCreate (options: any) {
let cs = new CardGameState();
this.setMetadata({rank: 2});
this.setState(cs);
this.setPrivate(true);
if (options.count) {
this.robotCount = Math.min(Math.max(0, options.count), this.maxClients - 1);
}
if (options.match) {
this.match = options.match;
}
this.battleMan.init(cs, this);
this.clock.start();
this.state.gameState = GameStateConst.STATE_WAIT_JOIN;
@ -105,6 +113,7 @@ export class GeneralRoom extends Room {
let data = {
client: client,
accountId: options.accountid,
seat: options.seat
};
this.dispatcher.dispatch(new OnJoinCommand(), data);
}

View File

@ -0,0 +1,393 @@
import {Client, generateId, matchMaker, Room} from "colyseus";
interface MatchmakingGroup {
averageRank: number;
clients: ClientStat[],
priority?: boolean;
ready?: boolean;
confirmed?: number;
count: number;
// cancelConfirmationTimeout?: Delayed;
}
interface ClientStat {
clients: Map<string, {client: Client, options: any, seat?: number}>;
waitingTime: number;
options?: any;
groupTag?: string;
group?: MatchmakingGroup;
rank: number;
confirmed?: boolean;
}
export class RankedLobbyRoom extends Room {
/**
* If `allowUnmatchedGroups` is true, players inside an unmatched group (that
* did not reached `numClientsToMatch`, and `maxWaitingTime` has been
* reached) will be matched together. Your room should fill the remaining
* spots with "bots" on this case.
*/
allowUnmatchedGroups: boolean = true;
/**
* Evaluate groups for each client at interval
*/
evaluateGroupsInterval = 1000;
/**
* Groups of players per iteration
*/
groups: MatchmakingGroup[] = [];
/**
* name of the room to create
*/
roomToCreate = "general_room";
/**
*
*/
maxWaitingTime = 15 * 1000;
/**
* ,
* @type {number}
*/
groupAddOneTime = 10 * 1000;
/**
* after this time, try to fit this client with a not-so-compatible group
*/
maxWaitingTimeForPriority?: number = 10 * 1000;
/**
* number of players on each match
*/
numClientsToMatch = 4;
// /**
// * after a group is ready, clients have this amount of milliseconds to confirm
// * connection to the created room
// */
// cancelConfirmationAfter = 5000;
maxDiffRatio = 1.2;
/**
* rank and group cache per-player
*/
stats: ClientStat[] = [];
onCreate(options: any) {
if (options.maxWaitingTime) {
this.maxWaitingTime = options.maxWaitingTime;
}
if (options.numClientsToMatch) {
this.numClientsToMatch = options.numClientsToMatch;
}
this.onMessage("bye", (client: Client, message: any) => {
const stat = this.stats.find(stat => stat.clients.has(client.sessionId));
if (stat && stat.group && typeof (stat.group.confirmed) === "number") {
stat.confirmed = true;
stat.group.confirmed++;
stat.clients.delete(client.sessionId);
client.leave();
}
})
/**
* Redistribute clients into groups at every interval
*/
this.setSimulationInterval(() => this.redistributeGroups(), this.evaluateGroupsInterval);
}
onJoin(client: Client, options: any) {
/**
* group, group相同的记录
* ,
* , clients已经查过2条,
* , clients是1, client添加到该记录中, rank
*/
if (options.group) {
let length = this.stats.length;
let groupData;
for (let i = 0; i < length; i++) {
if (this.stats[i].groupTag == options.group) {
groupData = this.stats[i];
break
}
}
if (!groupData) {
let clientMap = new Map();
clientMap.set(client.sessionId, {client, options});
groupData = {
clients: clientMap,
rank: options.rank,
groupTag: options.group,
waitingTime: 0,
options
}
this.stats.push(groupData);
} else {
if (groupData.clients.size >= 2) {
let clientMap = new Map();
options.group = generateId();
clientMap.set(client.sessionId, {client, options});
this.stats.push({
clients: clientMap,
rank: options.rank,
waitingTime: 0,
options
});
} else {
groupData.clients.set(client.sessionId, {client, options});
groupData.rank = (groupData.rank + options.rank) / 2
}
}
client.send("clients", groupData.clients.size);
} else {
let clientMap = new Map();
clientMap.set(client.sessionId, {client, options});
this.stats.push({
clients: clientMap,
rank: options.rank,
waitingTime: 0,
options
});
}
client.send("clients", 1);
}
createGroup() {
let group: MatchmakingGroup = {clients: [], averageRank: 0, count: 0};
this.groups.push(group);
return group;
}
redistributeGroups() {
// re-set all groups
this.groups = [];
const stats = this.stats.sort((a, b) => a.rank - b.rank);
let currentGroup: MatchmakingGroup = this.createGroup();
let totalRank = 0;
// 先过滤一边组队的情况
for (let i = 0, l = stats.length; i < l; i++) {
if (stats[i].clients.size == 1) {
continue;
}
const stat = stats[i];
stat.waitingTime += this.clock.deltaTime;
if (stat.group && stat.group.ready) {
continue;
}
if (currentGroup.averageRank > 0) {
const diff = Math.abs(stat.rank - currentGroup.averageRank);
const diffRatio = (diff / currentGroup.averageRank);
/**
* figure out how to identify the diff ratio that makes sense
*/
if (diffRatio > this.maxDiffRatio) {
currentGroup = this.createGroup();
totalRank = 0;
}
}
stat.group = currentGroup;
currentGroup.clients.push(stat);
currentGroup.count += stat.clients.size;
totalRank += stat.rank;
currentGroup.averageRank = totalRank / currentGroup.clients.length;
if (currentGroup.count === this.numClientsToMatch) {
currentGroup.ready = true;
currentGroup = this.createGroup();
totalRank = 0;
}
}
totalRank = 0;
currentGroup = this.createGroup();
for (let i = 0, l = stats.length; i < l; i++) {
const stat = stats[i];
if (stats[i].clients.size == 1) {
stat.waitingTime += this.clock.deltaTime;
} else {
if (stat.waitingTime < this.groupAddOneTime) {
continue;
}
}
/**
* do not attempt to re-assign groups for clients inside "ready" groups
*/
if (stat.group && stat.group.ready) {
continue;
}
/**
* Force this client to join a group, even if rank is incompatible
*/
// if (
// this.maxWaitingTimeForPriority !== undefined &&
// stat.waitingTime >= this.maxWaitingTimeForPriority
// ) {
// currentGroup.priority = true;
// }
if (
currentGroup.averageRank > 0 &&
!currentGroup.priority
) {
const diff = Math.abs(stat.rank - currentGroup.averageRank);
const diffRatio = (diff / currentGroup.averageRank);
/**
* figure out how to identify the diff ratio that makes sense
*/
if (diffRatio > this.maxDiffRatio) {
currentGroup = this.createGroup();
totalRank = 0;
}
}
stat.group = currentGroup;
currentGroup.clients.push(stat);
currentGroup.count += stat.clients.size;
totalRank += stat.rank;
currentGroup.averageRank = totalRank / currentGroup.count;
if (
(currentGroup.count === this.numClientsToMatch) ||
/**
* Match long-waiting clients with bots
* FIXME: peers of this group may be entered short ago
*/
(stat.waitingTime >= this.maxWaitingTime && this.allowUnmatchedGroups)
) {
currentGroup.ready = true;
currentGroup = this.createGroup();
totalRank = 0;
}
}
this.checkGroupsReady();
}
generateSeat(index: number): number {
switch (index) {
case 0:
return 0;
case 1:
return 4;
case 2:
return 1;
case 3:
return 2;
}
}
async checkGroupsReady() {
await Promise.all(
this.groups
.map(async (group) => {
if (group.ready) {
group.confirmed = 0;
/**
* Create room instance in the server.
*/
const room = await matchMaker.createRoom(this.roomToCreate, {rank: true, count: this.numClientsToMatch - group.count});
// TODO: 预处理数据, 确定座次
let hasGroup = false;
for (let client of group.clients) {
if (client.clients.size > 1) {
hasGroup = true;
break;
}
}
let seat = 0;
if (hasGroup) {
for (let client of group.clients) {
if (client.clients.size > 1) {
for (let [,sub] of client.clients) {
sub.seat = this.generateSeat(seat ++);
}
}
}
for (let client of group.clients) {
if (client.clients.size == 1) {
for (let [,sub] of client.clients) {
sub.seat = this.generateSeat(seat ++);
}
}
}
} else {
group.clients.sort((a, b) => a.rank - b.rank);
for (let client of group.clients) {
for (let [,sub] of client.clients) {
sub.seat = seat ++;
}
}
}
await Promise.all(group.clients.map(async (client) => {
const matchData = await matchMaker.reserveSeatFor(room, client.options);
/**
* Send room data for new WebSocket connection!
*/
for (let [,data] of client.clients) {
let options: any = {seat: data.seat, rank: data.options.rank};
Object.assign(options, matchData);
data.client.send("match_success", options);
}
}));
// /**
// * Cancel & re-enqueue clients if some of them couldn't confirm connection.
// */
// group.cancelConfirmationTimeout = this.clock.setTimeout(() => {
// group.clients.forEach(stat => {
// this.send(stat.client, 0);
// stat.group = undefined;
// stat.waitingTime = 0;
// });
// }, this.cancelConfirmationAfter);
} else {
/**
* Notify all clients within the group on how many players are in the queue
*/
group.clients.forEach(client => {
for (let [,data] of client.clients) {
data.client.send("clients", group.count);
}
});
}
})
);
}
onLeave(client: Client, consented: boolean) {
const stat = this.stats.find(stat => stat.clients.has(client.sessionId));
if (stat.clients.size > 1) {
stat.clients.delete(client.sessionId);
let data = [...stat.clients.values()][0];
stat.rank = data.options.rank;
} else {
this.stats.remove(stat);
}
}
onDispose() {
}
}

View File

@ -10,17 +10,27 @@ import {GameEnv} from "../../cfg/GameEnv";
*/
export class OnJoinCommand extends Command<CardGameState, {
client: Client,
accountId: string
accountId: string,
seat?: number,
}> {
execute({client, accountId} = this.payload) {
execute({client, accountId, seat} = this.payload) {
let count = this.state.players.size;
let team = (count == 1 || count == 2)? 1 : 0;
let player = new Player(client.sessionId, count, team);
if (count >= this.room.maxClients) {
return;
}
if (accountId && accountId == 'robot') {
accountId = `robot_${client.sessionId}`;
} else if (!accountId) {
accountId = `player_${client.sessionId}`;
}
let team = 0;
let idx = count;
if (seat != undefined) {
idx = +seat;
} else {
team = (count == 1 || count == 2) ? 1 : 0;
}
let player = new Player(client.sessionId, idx, team);
player.accountId = accountId;
this.state.players.set(client.sessionId, player);
this.room.addAssistClient(client.sessionId);
@ -42,9 +52,16 @@ export class OnJoinCommand extends Command<CardGameState, {
self.room.addScheduleTime(moreTime, 'play_join', 'waiting_player')
}
if (this.state.players.size >= this.room.maxClients) {
this.room.lock().then(() => {});
this.room.lock().then(() => {
});
this.state.updateGameState(GameStateConst.STATE_WAIT_PREPARE);
} else if (this.state.players.size < this.room.maxClients
&& this.state.players.size >= this.room.maxClients - this.room.robotCount) {
for (let i = 0; i < this.room.robotCount; i++) {
self.room.addRobot();
}
}
this.room.bUserJoin(`${client.sessionId}`, {except: client});
}

View File

@ -1,4 +1,11 @@
import {ArraySchema, filter, MapSchema, Schema, SetSchema, type} from "@colyseus/schema";
import {
ArraySchema,
filter,
MapSchema,
Schema,
SetSchema,
type
} from "@colyseus/schema";
import {Pet} from "./Pet";
import {Card} from "./Card";
import {GameEnv} from "../../cfg/GameEnv";
@ -11,6 +18,11 @@ export class Player extends Schema {
id: string;
accountId: string;
/**
*
* @type {string}
*/
group?: string;
@type("number")
heroId: number;