3 Star 1 Fork 0

UchihaSasuka/ultimate-werewolf-service

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
index.js 42.40 KB
一键复制 编辑 原始数据 按行查看 历史
liaokun 提交于 2024-07-29 20:06 . 进入房间时传入本地缓存头像
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const dotenv = require("dotenv");
const cors = require("cors");
const fs = require("fs");
const path = require("path");
const morgan = require("morgan");
const logger = require("./logger");
const lodash = require("lodash");
dotenv.config();
const app = express();
const server = http.createServer(app);
app.use((err, req, res, next) => {
logger.error(`服务器异常: ${err.message}`);
res.status(500).send("服务器异常");
});
// 捕获未捕获的异常
process.on("uncaughtException", (err) => {
logger.error(err); // 直接记录错误对象,以便包含堆栈信息
});
process.on("unhandledRejection", (reason, promise) => {
logger.error(reason); // 直接记录拒绝原因,以便包含堆栈信息
});
app.get("/async-error", async (req, res, next) => {
try {
// 模拟异步错误
await someAsyncFunction();
res.send("This will not be reached if an error occurs");
} catch (err) {
next(err); // 将错误传递给错误处理中间件
}
});
const allowedOrigins = [
"http://117.72.8.112:9000",
"http://117.72.8.112:7396",
"http://localhost:9000",
"http://192.168.50.44:9000",
"http://lllkk.fun:9000",
"https://werewolf.lllkk.fun",
];
const io = new Server(server, {
cors: {
origin: (origin, callback) => {
if (allowedOrigins.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
methods: ["GET", "POST"],
},
pingInterval: 10000,
pingTimeout: 5000,
});
app.use(
cors({
origin: (origin, callback) => {
if (allowedOrigins.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
methods: ["GET", "POST"], // 可选的,定义允许的方法
})
);
const rooms = {};
const heartbeats = {}; // 存储心跳检测任务
const baseAbilities = {
viewPlayer: {
name: "viewPlayer",
execute: (game, id, data) => {
if (data.target1 && data.target1.type === "player") {
const player = game.players.find(
(player) => player.username === data.target1.name
);
if (player) {
game.logAction(
id,
logType.player.type,
`查看了玩家${getPlayerIndex(game.players, player.username)}-${
player.username
}的身份,他是${player.role.name}`
);
return true;
}
}
return false;
},
},
viewDeck: {
name: "viewDeck",
execute: (game, id, data) => {
if (data && data.target1 && data.target1.type === "deck") {
const index = data.target1.name;
// 判断是否需要存储被查看的卡牌
const player = game.players.find((p) => p.id === id);
if (player) {
// 校验是否已经验过该卡牌
if (player.seenArray && player.seenArray.length > 0) {
if (
player.seenArray.some(
(item) =>
item.name === data.target1.name &&
item.type === data.target1.type
)
) {
return false;
}
}
// 判断是否需要存储为交换对象
if (
player.initialRole.extParams &&
player.initialRole.extParams.storeView
) {
player.toBeSwap = {};
player.toBeSwap.type = "deck";
player.toBeSwap.name = index;
}
game.logAction(
id,
logType.player.type,
`查看了底牌${deckName[index]},他是${game.leftoverCards[index].name}`
);
if (!player.seenArray) {
player.seenArray = [];
}
player.seenArray.push(data.target1);
return true;
}
} else {
return false;
}
},
},
swapCard: {
name: "swapCard",
execute: (game, id, data) => {
logInfo = "交换了";
// 如果只有一个对象则默认自己
if (!data.target2) {
const currentPlayer = game.players.find((player) => player.id == id);
data.target2 = {};
if (currentPlayer.toBeSwap) {
data.target2.type = currentPlayer.toBeSwap.type;
data.target2.name = currentPlayer.toBeSwap.name;
} else {
data.target2.type = "player";
data.target2.name = currentPlayer.username;
}
}
if (data.target1.type === "deck") {
logInfo = logInfo.concat("底牌", deckName[data.target1.name]);
} else if (data.target1.type === "player") {
logInfo = logInfo.concat(
`玩家${getPlayerIndex(game.players, data.target1.name)}-`,
data.target1.name,
"的身份"
);
}
logInfo = logInfo.concat("");
if (data.target2.type === "deck") {
// 换牌逻辑
if (data.target1.type === "deck") {
[
game.leftoverCards[data.target1.name],
game.leftoverCards[data.target2.name],
] = [
game.leftoverCards[data.target2.name],
game.leftoverCards[data.target1.name],
];
} else {
const player = game.players.find(
(player) => player.username === data.target1.name
);
if (!player.lock) {
[game.leftoverCards[data.target2.name], player.role] = [
player.role,
game.leftoverCards[data.target2.name],
];
}
}
logInfo = logInfo.concat("底牌", deckName[data.target2.name]);
} else if (data.target2.type === "player") {
// 换牌逻辑
if (data.target1.type === "deck") {
const player = game.players.find(
(player) => player.username === data.target2.name
);
if (!player.lock) {
[game.leftoverCards[data.target1.name], player.role] = [
player.role,
game.leftoverCards[data.target1.name],
];
}
} else {
const player1 = game.players.find(
(player) => player.username === data.target1.name
);
const player2 = game.players.find(
(player) => player.username === data.target2.name
);
if (!player1.lock && !player2.lock) {
[player1.role, player2.role] = [player2.role, player1.role];
}
}
logInfo = logInfo.concat(
`玩家${getPlayerIndex(game.players, data.target2.name)}-`,
data.target2.name,
"的身份"
);
}
game.logAction(id, logType.player.type, logInfo);
return true;
},
},
votePlayer: {
name: "votePlayer",
execute: (game, id, data) => {
if (data.target1 && data.target1.type === "player") {
const currentPlayer = game.players.find((player) => player.id == id);
const targetPlayer = game.players.find(
(player) => player.username === data.target1.name
);
currentPlayer.target = targetPlayer.username;
game.logAction(
id,
logType.player.type,
`你指定了玩家${getPlayerIndex(game.players, targetPlayer.username)}-${
targetPlayer.username
},他出局你获得胜利`
);
return true;
} else {
return false;
}
},
},
lockPlayer: {
name: "lockPlayer",
execute: (game, id, data) => {
if (data.target1 && data.target1.type === "player") {
const player = game.players.find(
(player) => player.username === data.target1.name
);
if (player) {
player.lock = true;
game.logAction(
id,
logType.player.type,
`你指定了锁定了玩家${getPlayerIndex(
game.players,
player.username
)}-${player.username},他不会被交换`
);
return true;
}
} else {
return false;
}
},
},
beWolf: {
name: "beWolf",
execute: (game, id, data) => {
if (data.target1 && data.target1.type === "player") {
const player = game.players.find(
(player) => player.username === data.target1.name
);
if (player) {
player.role = defaultRoles.find((role) => role.name === "狼人");
game.logAction(
id,
logType.player.type,
`你将玩家${getPlayerIndex(game.players, player.username)}-${
player.username
}变为了狼人`
);
return true;
}
} else {
return false;
}
},
},
};
const nagativeAbilities = {
seeWolfs: {
name: "seeWolfs",
execute: (game, id) => {
// 狼人确认队友
const werewolves = game.players.filter(
(player) => player.initialRole.faction === "wolf"
);
if (werewolves.length > 0) {
const werewolfNames = werewolves
.map((wolf) => wolf.username)
.join(", ");
game.logAction(id, logType.system.type, `本局狼人有: ${werewolfNames}`);
}
},
},
seeMasons: {
name: "seeMasons",
execute: (game, id) => {
const masons = game.players.filter(
(player) => player.initialRole.name === "守夜人"
);
const masonNames = masons.map((mason) => mason.username).join(", ");
game.logAction(id, logType.system.type, `本局守夜人有: ${masonNames}`);
},
},
sigleWolf: {
name: "sigleWolf",
execute: (game) => {
// 如果只有一个狼人,允许其查看一张底牌
const werewolves = game.players.filter(
(player) => player.initialRole.faction === "wolf"
);
if (werewolves.length === 1) {
werewolves[0].initialRole.abilities.push(abilities.viewDeck);
game.logAction(
werewolves[0].id,
logType.system.type,
`你是唯一的狼人,你可以查看一张底牌。`
);
}
},
},
seeSelf: {
name: "seeSelf",
execute: (game, id) => {
// 失眠者查看自己身份呢
const player = game.players.find((player) => player.id === id);
game.logAction(
id,
logType.system.type,
`你现在的身份是${player.role.name}`
);
},
},
randomMaker: {
name: "randomMaker",
execute: (game, id) => {
const n = game.players.length;
// 小精灵:根据玩家数量随机交换包括自己的两张牌
const num1 = getRandomInt(n);
let num2 = getRandomInt(n);
// 确保 num2 不与 num1 重复
while (num2 === num1) {
num2 = getRandomInt(n);
}
player1 = game.players[num1];
player2 = game.players[num2];
if (!player1.lock && !player2.lock) {
[player1.role, player2.role] = [player2.role, player1.role];
}
game.logAction(
id,
logType.player.type,
`系统随机交换了玩家${getPlayerIndex(game.players, player1.username)}-${
player1.username
}与玩家${getPlayerIndex(game.players, player2.username)}-${
player2.username
}的身份`
);
},
},
sigleMinion: {
name: "sigleMinion",
execute: (game) => {
// 如果没有狼人
const werewolves = game.players.filter(
(player) => player.initialRole.faction === "wolf"
);
if (werewolves.length == 0) {
const player = game.players.find(
(player) => player.initialRole.faction === "minion"
);
if (player) {
player.initialRole.faction = "wolf";
game.logAction(
player.id,
logType.system.type,
`本局没有狼人,你将转换为狼人阵营`
);
}
}
},
},
};
const abilities = {
viewHand: {
name: "查看手牌",
max: 1,
base: [baseAbilities.viewPlayer],
opType: ["player"],
},
swapHand: {
name: "交换手牌",
max: 1,
base: [baseAbilities.swapCard],
opType: ["player", "player"],
},
randomHandSwap: {
name: "随机交换两张手牌",
max: 1,
nagative: [nagativeAbilities.randomMaker],
},
seerViewDeck: {
name: "预言家查看底牌",
max: 2,
base: [baseAbilities.viewDeck],
opType: ["deck"],
},
viewDeck: {
name: "查看底牌",
max: 1,
base: [baseAbilities.viewDeck],
opType: ["deck"],
},
viewAndSwap: {
name: "查看并交换手牌",
max: 1,
base: [baseAbilities.viewPlayer, baseAbilities.swapCard],
opType: ["player"],
},
seeWolfs: { name: "确认狼人", nagative: [nagativeAbilities.seeWolfs] },
seeMasons: { name: "确认守夜人", nagative: [nagativeAbilities.seeMasons] },
viewSelfHand: {
name: "确认自己手牌",
max: 1,
nagative: [nagativeAbilities.seeSelf],
},
swapSelfHandDeck: {
name: "交换手牌和任意一张底牌",
max: 1,
base: [baseAbilities.swapCard],
opType: ["deck"],
},
viewAndSteal: {
name: "查看并交换底牌",
max: 1,
base: [baseAbilities.viewDeck, baseAbilities.swapCard],
opType: ["deck"],
},
lockPlayer: {
name: "锁定玩家身份",
max: 1,
base: [baseAbilities.lockPlayer],
opType: ["player"],
},
votePlayer: {
name: "票出该玩家则获胜",
max: 1,
base: [baseAbilities.votePlayer],
opType: ["player"],
},
swapDeckAndHand: {
name: "将一张牌交换给某位玩家",
max: 1,
base: [baseAbilities.swapCard],
opType: ["player"],
order: 2,
},
beWolf: {
name: "将某位玩家变为狼人",
max: 1,
base: [baseAbilities.beWolf],
opType: ["player"],
},
gameBegin: {
name: "游戏开始时的系统执行能力",
nagative: [nagativeAbilities.sigleWolf, nagativeAbilities.sigleMinion],
},
};
const logType = {
system: { name: "系统提示", type: "1" },
player: { name: "玩家操作", type: "2" },
};
const defaultRoles = [
{
name: "狼人",
description: "夜晚确认同伴身份,独狼可查看一张底牌",
img: "/werewolf.png",
count: 2,
abilities: [abilities.seeWolfs],
abilitiesCount: 1,
faction: "wolf",
phase: "狼人",
},
{
name: "预言家",
description: "夜晚可查看一名其他玩家的牌或者两张底牌",
img: "/seer.png",
count: 1,
abilities: [abilities.viewHand, abilities.seerViewDeck],
abilitiesCount: 1,
faction: "village",
phase: "预言家",
},
{
name: "捣蛋鬼",
description: "夜晚可选择两名其他玩家,交换他们的身份",
img: "/troublemaker.png",
count: 1,
abilities: [abilities.swapHand],
abilitiesCount: 2,
faction: "village",
phase: "捣蛋鬼",
},
{
name: "强盗",
description: "夜晚可选择一名其他玩家,查看他的身份并与自己交换",
img: "/robber.png",
count: 1,
abilities: [abilities.viewAndSwap],
abilitiesCount: 1,
faction: "village",
phase: "强盗",
},
{
name: "村民",
description: "没有特殊能力,白天参与投票",
img: "/villager.png",
count: 0,
abilities: [],
abilities: 0,
faction: "village",
phase: "村民",
},
{
name: "失眠者",
description: "天亮前可查看自己的最新身份",
img: "/insomniac.png",
count: 1,
abilities: [abilities.viewSelfHand],
abilitiesCount: 0,
faction: "village",
phase: "失眠者",
},
{
name: "爪牙",
description: "夜晚可知道场上的狼人牌,白天被投出去算狼人阵营获胜",
img: "/minion.png",
count: 0,
abilities: [abilities.seeWolfs],
abilitiesCount: 0,
faction: "minion",
phase: "狼人",
},
{
name: "狼先知",
description: "在狼人基础上,可查看一名其他玩家的牌",
img: "/mystic.png",
count: 0,
abilities: [abilities.viewHand, abilities.seeWolfs],
abilitiesCount: 2,
faction: "wolf",
phase: "狼先知",
},
{
name: "守夜人",
description: "成对加入对局,夜晚睁眼查看守夜人队友",
img: "/mason.png",
count: 0,
abilities: [abilities.seeMasons],
abilitiesCount: 0,
faction: "village",
phase: "守夜人",
},
{
name: "酒鬼",
description: "夜晚可选择一张底牌交换给自己,不知道自己的最新身份",
img: "/drunk.png",
count: 0,
abilities: [abilities.swapSelfHandDeck],
abilitiesCount: 1,
faction: "village",
phase: "酒鬼",
},
{
name: "皮匠",
description: "被投出去则自己单独获胜",
img: "/Tanner.png",
count: 0,
abilities: [],
abilitiesCount: 0,
faction: "tanner",
phase: "皮匠",
},
{
name: "小精灵",
description: "随机交换两名玩家的身份(包括自己)",
img: "/newtroublemaker.jpg",
count: 0,
abilities: [abilities.randomHandSwap],
abilitiesCount: 0,
faction: "village",
phase: "小精灵",
},
{
name: "盗贼",
description: "夜晚可选择一张底牌交换给自己,并查看自己的最新身份",
img: "/curse.png",
count: 0,
abilities: [abilities.viewAndSteal],
abilitiesCount: 1,
faction: "village",
phase: "盗贼",
},
{
name: "诅咒者",
description: "指定一名玩家,该玩家被投出去则自己获胜",
img: "/curse.jpg",
count: 0,
abilities: [abilities.votePlayer],
abilitiesCount: 1,
faction: "curse",
phase: "诅咒者",
},
{
name: "哨兵",
description: "守护一名玩家,使其身份不会被换",
img: "/sentinel.png",
count: 0,
abilities: [abilities.lockPlayer],
abilitiesCount: 1,
faction: "village",
phase: "哨兵",
},
{
name: "见习预言家",
description: "夜晚可查看一张底牌",
img: "/apprentice.png",
count: 0,
abilities: [abilities.viewDeck],
abilitiesCount: 1,
faction: "village",
phase: "预言家",
},
{
name: "女巫",
description: "查看一张底牌并将他交换给某位玩家",
img: "/witch.png",
count: 0,
abilities: [abilities.viewDeck, abilities.swapDeckAndHand],
extParams: { storeView: true },
abilitiesCount: 2,
faction: "village",
phase: "女巫",
},
{
name: "阿尔法狼",
description: "将某位玩家变成狼人",
img: "/alpha.jpg",
count: 0,
abilities: [abilities.seeWolfs, abilities.beWolf],
abilitiesCount: 2,
faction: "wolf",
phase: "阿尔法狼",
},
{
name: "冲锋狼",
description: "投票结算时少算一票",
img: "/crashwolf.png",
count: 0,
abilities: [abilities.seeWolfs],
abilitiesCount: 1,
faction: "wolf",
phase: "狼人",
},
];
const majorPhases = ["夜晚", "白天"];
const nightSubPhases = [
{
name: "准备阶段",
actionTime: 10,
},
{
name: "爪牙",
actionTime: 0,
},
{
name: "狼人",
actionTime: 8,
},
{
name: "狼先知",
actionTime: 15,
},
{
name: "阿尔法狼",
actionTime: 15,
},
{
name: "守夜人",
actionTime: 0,
},
{
name: "诅咒者",
actionTime: 8,
},
{
name: "预言家",
actionTime: 10,
},
{
name: "见习预言家",
actionTime: 8,
},
{
name: "哨兵",
actionTime: 8,
},
{
name: "强盗",
actionTime: 8,
},
{
name: "女巫",
actionTime: 10,
},
{
name: "捣蛋鬼",
actionTime: 10,
},
{
name: "小精灵",
actionTime: 0,
},
{
name: "酒鬼",
actionTime: 8,
},
{
name: "盗贼",
actionTime: 8,
},
{
name: "失眠者",
actionTime: 0,
},
];
const daySubPhases = ["讨论环节", "投票环节", "结算环节"];
const deckName = ["A", "B", "C"];
const calculateBeforeFilter = [
{
name: "crashWolf",
filt: (game, voteCounts) => {
// 找到这把玩家中的冲锋狼
player = game.players.find((p) => p.role.name === "冲锋狼");
if (player) {
// 票数减1
const vote = voteCounts[player.username];
if (vote) {
voteCounts[player.username] = vote - 1;
}
}
},
},
];
const victoryConditions = {
1: {
name: "minion",
condition: (game, mostVotedPlayers) => {
const minionPlayer = game.players.find(
(p) => p.role.faction === "minion"
);
return (
minionPlayer &&
mostVotedPlayers.some(
(playerName) => playerName === minionPlayer.username
)
);
},
desc: "狼人阵营",
},
2: {
name: "tanner",
condition: (game, mostVotedPlayers) => {
const tannerPlayer = game.players.find(
(p) => p.role.faction === "tanner"
);
return (
tannerPlayer &&
mostVotedPlayers.some(
(playerName) => playerName === tannerPlayer.username
)
);
},
desc: "皮匠",
},
3: {
name: "village",
condition: (game, mostVotedPlayers) => {
const wolfs = game.players
.filter((p) => p.role.faction === "wolf")
.map((p) => p.username);
return (
wolfs.length == 0 ||
mostVotedPlayers.some((playerName) => wolfs.includes(playerName))
);
},
desc: "好人阵营",
},
4: {
name: "wolf",
condition: (game, mostVotedPlayers) => {
const wolfs = game.players
.filter((p) => p.role.faction === "wolf")
.map((p) => p.username);
return (
wolfs.length > 0 &&
mostVotedPlayers.every((playerName) => !wolfs.includes(playerName))
);
},
desc: "狼人阵营",
},
};
const separateVictory = [
{
name: "curse",
condition: (game, mostVotedPlayers) => {
// 如果有诅咒者诅咒的目标 则自己获胜
const cursePlayer = game.players.find((p) => p.target);
return (
cursePlayer &&
mostVotedPlayers.some((playerName) => cursePlayer.target === playerName)
);
},
desc: "诅咒者",
},
];
class Game {
constructor(room) {
this.room = room;
this.preRoles = [];
this.players = [];
this.leftoverCards = [];
this.majorPhase = "夜晚";
this.subPhase = "狼人";
this.logs = {}; // 操作记录
this.voteResults = []; // 投票结果
this.discussionInfo = null;
this.started = false;
}
addPlayer(player) {
player.actions = {}; // 初始化 actions 属性
this.players.push(player);
}
setLeftoverCards(cards) {
this.leftoverCards = cards;
}
logAction(playerId, type, action) {
const player = this.players.find((p) => p.id === playerId);
if (player) {
if (!this.logs[player.username]) {
this.logs[player.username] = {};
}
if (!this.logs[player.username][type]) {
this.logs[player.username][type] = [];
}
this.logs[player.username][type].push(action);
}
}
logVoteResult(playerId, targetId) {
const currentName = this.players.find((p) => p.id === playerId)?.username;
const targetName = this.players.find((p) => p.id === targetId)?.username;
this.voteResults.push({ currentName, targetName });
}
getState() {
return {
players: this.players,
leftoverCards: this.leftoverCards,
majorPhase: this.majorPhase,
subPhase: this.subPhase,
logs: this.logs, // 返回操作记录
voteResults: this.voteResults, // 返回投票结果
discussionInfo: this.discussionInfo,
started: this.started,
nightSubPhases: this.phases, // 行动顺序
curActionTime:
this.started && this.majorPhase === "夜晚"
? nightSubPhases.find((sub) => sub.name == this.subPhase).actionTime -
Math.round((Date.now() - this.nextPhaseTaskTime) / 1000)
: null,
winner: this.winner,
};
}
startDiscussionPhase() {
const randomPlayerIndex = Math.floor(Math.random() * this.players.length);
const directions = ["顺时针", "逆时针"];
const randomDirection =
directions[Math.floor(Math.random() * directions.length)];
this.discussionInfo = {
startingPlayer: this.players[randomPlayerIndex],
direction: randomDirection,
index: randomPlayerIndex,
};
}
}
// 随机洗牌函数
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
function getActionTime() {
return;
}
/**
* 获胜判定
* @param {投票结果} voteCounts
* @param {游戏对象} game
* @returns
*/
function checkVictory(voteCounts, game) {
const maxVotes = Math.max(...Object.values(voteCounts));
const mostVotedPlayers = Object.keys(voteCounts).filter(
(playerName) => voteCounts[playerName] === maxVotes
);
const victoryGroup = [];
// 循环遍历结算规则
// 1.1 单独结算判定
separateVictory.forEach((vc) => {
if (vc.condition(game, mostVotedPlayers)) {
victoryGroup.push(vc.desc);
}
});
// 1.2 综合判定
const keys = Object.keys(victoryConditions).map((key) => parseInt(key));
keys.sort((a, b) => a - b);
keys.some((key) => {
vc = victoryConditions[key];
if (vc.condition(game, mostVotedPlayers)) {
victoryGroup.push(vc.desc);
return true;
}
});
return victoryGroup;
}
function nigAction(room) {
const game = rooms[room].game;
subPhase = nightSubPhases.find((sp) => sp.name === game.subPhase);
// 查找当前阶段是否有玩家
const players = game.players.filter(
(player) =>
player.initialRole.phase && subPhase.name === player.initialRole.phase
);
// 执行被动
if (players) {
players.forEach((curPlayer) => {
curPlayer.initialRole.abilities.forEach((ability) => {
if (ability.nagative) {
ability.nagative.forEach((na) => {
nagativeAbilities[na.name].execute(game, curPlayer.id);
});
}
});
});
}
if (subPhase.actionTime > 0) {
if (!game.nextPhaseTask) {
// 开启一个定时任务,结束时推进到下一个阶段
const intervalId = setTimeout(() => {
// 等待推进到下一个阶段
game.nextPhaseTask = null;
nextPhase(room);
}, subPhase.actionTime * 1000);
// 确保只有一个
game.nextPhaseTask = intervalId;
game.nextPhaseTaskTime = Date.now();
}
} else {
// 直接推进到下一个阶段
nextPhase(room);
}
}
/**
* 推荐到下一个阶段
* 夜晚阶段设置推进倒计时,到时间自动推进到下一个阶段
* @param {} room
*/
function nextPhase(room) {
if (rooms[room]) {
const game = rooms[room].game;
// 检查是否已经存在定时任务并清除
if (game.nextPhaseTask) {
clearTimeout(game.nextPhaseTask);
game.nextPhaseTask = null;
}
if (game.majorPhase === "夜晚") {
currentSubPhaseIndex =
game.phases.indexOf(game.subPhase) > -1
? game.phases.indexOf(game.subPhase)
: -1;
if (currentSubPhaseIndex < game.phases.length - 1) {
game.subPhase = game.phases[++currentSubPhaseIndex];
nigAction(room);
} else {
game.startDiscussionPhase();
game.majorPhase = "白天";
game.subPhase = daySubPhases[0];
}
} else if (game.majorPhase === "白天") {
const currentSubPhaseIndex = daySubPhases.indexOf(game.subPhase);
if (currentSubPhaseIndex < daySubPhases.length - 1) {
game.subPhase = daySubPhases[currentSubPhaseIndex + 1];
}
}
if (game.subPhase === "结算环节" && !game.victoryGroup) {
vote(game);
}
io.to(room).emit("updateGameState", game.getState());
}
}
function vote(game) {
// 所有人投票完成,计算票数并确定胜利者
const voteCounts = {};
game.voteResults.forEach((vote) => {
voteCounts[vote.targetName] = (voteCounts[vote.targetName] || 0) + 1;
});
// 先做结算前置条件过滤
calculateBeforeFilter.forEach((filter) => filter.filt(game, voteCounts));
game.winner = checkVictory(voteCounts, game).join("") + "获胜";
game.subPhase = "结算环节";
}
function getPlayerIndex(players, username) {
const playerIndex = players.findIndex((p) => p.username === username);
return playerIndex + 1;
}
io.on("connection", (socket) => {
console.log("a user connected", socket.id);
// 重新连接后需要查询并修改状态
socket.on("updatePlayer", ({ room, player }) => {
if (rooms[room]) {
const game = rooms[room].game;
const currentPlayer = game.players.find(
(p) => p.username === player.username
);
if (currentPlayer) {
currentPlayer.avatar = player.avatar;
io.to(room).emit("updatePlayers", game.players);
}
}
});
socket.on("createRoom", ({ id, username, avatar }, callback) => {
if (id) {
const game = new Game(id);
rooms[id] = { host: socket.id, game, roles: defaultRoles };
game.addPlayer({ id: socket.id, username, visibleCards: [], avatar });
socket.join(id);
callback({ status: "ok", id, roles: defaultRoles, host: socket.id });
io.to(id).emit("updatePlayers", game.players); // 广播房主的玩家信息
io.to(id).emit("updateRoles", defaultRoles); // 广播默认角色配置信息
} else {
callback({ status: "error", message: "无法创建房间" });
}
});
socket.on("joinRoom", ({ room, username, avatar }, callback) => {
if (rooms[room]) {
const game = rooms[room].game;
const playerExists = game.players.some(
(player) => player.username === username
);
let notify = false;
if (!playerExists) {
if (!game.started && username && username.trim().length !== 0) {
game.addPlayer({ id: socket.id, username, visibleCards: [], avatar });
io.to(room).emit("updatePlayers", game.players);
notify = true;
}
} else {
const player = game.players.find(
(player) => player.username === username
);
if (player) {
// 判断是否还是房主.更换房间的房主id
if (player.id === rooms[room].host) {
rooms[room].host = socket.id;
io.to(room).emit("updateHost", rooms[room].host);
}
player.id = socket.id;
player.offline = false;
player.avatar = avatar;
notify = true;
}
}
if (notify) {
socket.join(room);
socket.emit("updateGameState", game.getState());
}
callback({
status: "ok",
roles: rooms[room].roles,
players: game.players,
host: rooms[room].host,
});
} else {
callback({ status: "error", message: "房间不存在" });
}
});
socket.on("getRoomData", ({ room }, callback) => {
if (rooms[room]) {
const game = rooms[room].game;
callback({
players: game.players,
roles: rooms[room].roles,
host: rooms[room].host,
});
} else {
callback({ status: "error", message: "房间不存在" });
}
});
socket.on("updateRoles", ({ room, roles }) => {
if (rooms[room]) {
rooms[room].roles = roles;
io.to(room).emit("updateRoles", roles);
}
});
socket.on("addPlayerSlot", (room) => {
if (rooms[room]) {
const game = rooms[room].game;
game.addPlayer({ id: null, username: null, visibleCards: [], avatar: null });
io.to(room).emit("updatePlayers", game.players); // 广播玩家更新信息
}
});
socket.on("removePlayerSlot", (room) => {
if (rooms[room]) {
const game = rooms[room].game;
if (game.players.length > 0) {
game.players.pop();
io.to(room).emit("updatePlayers", game.players); // 广播玩家更新信息
}
}
});
socket.on("removePlayer", ({ room, index }) => {
if (rooms[room]) {
const game = rooms[room].game;
const player = game.players[index];
if (player && player.id !== socket.id) {
game.players.splice(index, 1);
io.to(room).emit("updatePlayers", game.players); // 广播玩家更新信息
}
}
});
socket.on("leaveRoom", ({ room, username }) => {
if (rooms[room]) {
const game = rooms[room].game;
const playerIndex = game.players.findIndex(
(p) => p.username === username
);
if (playerIndex !== -1) {
game.players.splice(playerIndex, 1);
io.to(room).emit("updatePlayers", game.players); // 广播玩家更新信息
if (rooms[room].host === socket.id && game.players.length > 0) {
rooms[room].host = game.players[0].id;
io.to(room).emit("updateHost", rooms[room].host);
io.to(game.players[0].id).emit("newHost");
}
if (game.players.length === 0) {
delete rooms[room];
}
}
}
socket.leave(room);
});
socket.on("startGame", ({ room }, callback) => {
if (rooms[room]) {
const game = rooms[room].game;
const roles = rooms[room].roles || defaultRoles; // 确保 roles 定义正确
// 使用房主的配置初始化角色
const allRoles = [];
roles.forEach((role) => {
for (let i = 0; i < role.count; i++) {
allRoles.push(lodash.cloneDeep(role));
}
});
// 检查配置是否符合要求
if (allRoles.length != game.players.length + 3) {
callback({ status: "error", message: "配置数必须是玩家数+3" });
return;
}
// 检查配置是否符合要求
if (
allRoles.filter(
(role) => role.faction === "wolf" || role.faction === "minion"
).length === 0
) {
callback({
status: "error",
message: "配置中至少需要有一个狼人阵营或爪牙",
});
return;
}
game.preRoles = roles;
let shuffledRoles = shuffle(allRoles);
// 确保至少有一个狼人或者爪牙
while (
shuffledRoles
.slice(0, game.players.length)
.filter(
(role) => role.faction === "wolf" || role.faction === "minion"
).length === 0
) {
shuffledRoles = shuffle(allRoles);
}
const players = game.players.map((player, index) => ({
...player,
role: shuffledRoles[index],
initialRole: shuffledRoles[index], // 添加初始角色
actions: {}, // 初始化 actions 属性
}));
const leftoverCards = shuffledRoles
.slice(players.length)
.map((card) => ({ ...card, seen: false }));
game.players = players;
game.leftoverCards = leftoverCards;
// 初始化阶段列表
const assignedRoles = players
.map((player) => player.role.phase)
.concat(leftoverCards.map((card) => card.phase));
const originLeftoverCards = leftoverCards.map((card) => ({
...card,
}));
game.originLeftoverCards = originLeftoverCards;
game.phases = nightSubPhases
.map((action) => action.name)
.filter((action) => assignedRoles.includes(action));
// 增加准备阶段
game.phases.unshift("准备阶段");
game.majorPhase = "夜晚";
game.subPhase = "系统";
// 执行系统能力
if (abilities.gameBegin.nagative) {
abilities.gameBegin.nagative.forEach((ability) => {
ability.execute(game);
});
}
nextPhase(room);
game.started = true;
io.to(room).emit("gameStarted", game.getState());
callback({ status: "ok", roles: roles });
}
});
socket.on("nightAction", ({ room, action, data }, callback) => {
if (rooms[room]) {
if (!data || data.length === 0) {
callback({ status: "error", message: "请选择正确的操作对象" });
}
// 操作对象不能相同
if (
data.target1 &&
data.target2 &&
data.target1.type === data.target2.type &&
data.target1.name === data.target2.name
) {
callback({ status: "error", message: "不能选择同一个操作对象" });
}
const game = rooms[room].game;
const currentPlayer = game.players.find((p) => p.id === socket.id);
// 1.校验当前用户是否拥有该能力,以及是否到达触发能力的阶段
const playerAbilities = currentPlayer.initialRole.abilities;
if (
game.majorPhase === "夜晚" &&
playerAbilities.some((a) => a.name === action) &&
game.subPhase === currentPlayer.initialRole.phase
) {
// 1.2 再次校验是否达到次数上限 第一是看当前能力操作次数是否达到上限 其次得看总的能力是否达到上限,最后看当前操作的能力是否还没操作完成
const ability = playerAbilities.find((a) => a.name === action);
if (
ability.max > (currentPlayer.actions[action] || 0) &&
(currentPlayer.executingName == null ||
ability.name === currentPlayer.executingName) &&
currentPlayer.initialRole.abilitiesCount >
(currentPlayer.finishedAbilities || 0)
) {
// 1.3 判断是否满足执行顺序
if (
ability.order &&
ability.order != (currentPlayer.finishedAbilities || 0) + 1
) {
callback({ status: "error", message: "请先完成前置操作" });
return;
}
ability.base.forEach((base) => {
if (baseAbilities[base.name].execute(game, socket.id, data)) {
currentPlayer.actions[action] =
(currentPlayer.actions[action] || 0) + 1;
}
});
// 如果操作次数达到上限 增加一次完成能力的次数
if (
currentPlayer.actions &&
ability.max === currentPlayer.actions[action]
) {
currentPlayer.finishedAbilities =
(currentPlayer.finishedAbilities || 0) + 1;
currentPlayer.executingName = null;
} else {
currentPlayer.executingName = ability.name;
}
socket.emit("updateGameState", game.getState()); // 仅向当前玩家广播游戏状态更新,包括操作记录
} else {
callback({ status: "error", message: "操作次数已用完" });
}
}
}
});
socket.on("nextPhase", ({ room }) => {
nextPhase(room);
});
// 处理投票逻辑
socket.on("vote", ({ room, targetId }, callback) => {
if (rooms[room]) {
const game = rooms[room].game;
const player = game.players.find((p) => p.id === socket.id);
if (!player || player.hasVoted) return;
const targetPlayer = game.players.find((p) => p.id === targetId);
game.voteResults.push({
playerName: player.username,
targetName: targetPlayer.username,
});
player.hasVoted = true;
game.logAction(
socket.id,
logType.system.type,
`投票了玩家${getPlayerIndex(game.players, targetPlayer.username)}-${
targetPlayer.username
}`
);
callback({ status: "ok", message: "投票成功" });
if (game.voteResults.length === game.players.length) {
vote(game);
}
io.to(room).emit("updateGameState", game.getState());
}
});
socket.on("resetGame", ({ room }) => {
const game = rooms[room].game;
if (!game) return;
game.players.forEach((player) => {
player.role = null;
player.initialRole = null;
player.hasVoted = false;
player.actions = {};
player.executingName = null;
player.finishedAbilities = 0;
player.nagativeExecuted = null;
player.lock = null;
player.target = null;
player.seenArray = null;
});
game.majorPhase = "夜晚";
game.subPhase = "";
game.voteResults = [];
game.logs = {};
game.leftoverCards = [];
game.winner = null;
game.discussionInfo = null;
game.started = false;
game.victoryGroup = null;
io.to(room).emit("restartGame", game);
});
socket.on("heartbeat", (username) => {
if (heartbeats[username]) {
clearInterval(heartbeats[username].intervalId);
delete heartbeats[username];
console.log("清除定时任务");
}
});
socket.on("heartbeat2", ({ room, username }) => {
if (rooms[room]) {
const game = rooms[room].game;
const player = game.players.find(
(player) => player.username === username
);
if (player) {
player.offline = false;
io.to(room).emit("updatePlayers", game.players);
}
io.to(socket.id).emit("updateGameState", game.getState());
}
});
socket.on("disconnect", () => {
console.log("user disconnected", socket.id);
for (const room of Object.keys(rooms)) {
const game = rooms[room].game;
// 不移除 只更新状态
const player = game.players.find((p) => p.id === socket.id);
if (player) {
player.offline = true;
//game.players.splice(playerIndex, 1);
io.to(room).emit("updatePlayers", game.players); // 广播玩家更新信息
if (rooms[room].host === socket.id) {
// 这里不采取直接交接的方式 而是创建一个定时任务检测30s是否重连
let elapsed = 0;
const intervalId = setInterval(() => {
elapsed += 5000; // 每5秒检查一次
console.log(
`Checking status for ${player.username} (${
elapsed / 1000
}s elapsed)`
);
if (!player.offline) {
clearInterval(intervalId);
delete heartbeats[player.username];
}
if (elapsed >= 30000) {
// 超过30秒后停止检查,并进行房主交接
clearInterval(intervalId);
const onlinePlayer = game.players.find((p) => !p.offline);
if (onlinePlayer) {
rooms[room].host = onlinePlayer.id;
io.to(room).emit("updateHost", rooms[room].host);
io.to(onlinePlayer.id).emit("newHost");
}
delete heartbeats[player.username];
}
}, 5000); // 每5秒检查一次
heartbeats[player.username] = { intervalId };
}
if (
game.players.every((player) => player.offline === true) &&
rooms.length > 50
) {
const roomToDelete = Object.keys(rooms).find((game) =>
game.players.every((player) => player.offline === true)
);
if (roomToDelete) {
delete rooms[roomToDelete];
}
}
}
}
});
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/wiseacre1996/ultimate-werewolf-service.git
git@gitee.com:wiseacre1996/ultimate-werewolf-service.git
wiseacre1996
ultimate-werewolf-service
ultimate-werewolf-service
master

搜索帮助