Matou-Garou / convex /aiTown /gameCycle.ts
Jofthomas's picture
Jofthomas HF staff
fixes
1c4cbbe
import { v, Infer, ObjectType } from 'convex/values';
import { Game } from './game';
import {
DAY_DURATION,
NIGHT_DURATION,
WWOLF_VOTE_DURATION,
PLAYER_KILL_VOTE_DURATION,
} from '../constants';
import { processVotes } from './voting';
import { parseLLMVotingResult } from './voting';
import { LLmvotingCallWerewolf } from './voting';
import { GameId } from './ids';
import { Player } from './player';
export type CycleState = 'Day' | 'WerewolfVoting' | 'Night' | 'PlayerKillVoting' | 'EndGame' | 'LobbyState'
const stateDurations: { [key in CycleState]: number } = {
Day: DAY_DURATION,
Night: NIGHT_DURATION,
WerewolfVoting: WWOLF_VOTE_DURATION,
PlayerKillVoting: PLAYER_KILL_VOTE_DURATION,
EndGame: Infinity,
LobbyState: Infinity
};
const normalCycle: CycleState[] = [
'Day',
'WerewolfVoting',
'Night',
'PlayerKillVoting',
];
const pushToGist = (averageCorrectVotes: number[]) => {
const token = process.env.GITHUB_TOKEN; // Read GitHub token from environment variables
const data = {
description: "Average Correct Votes",
public: true,
files: {
"averageCorrectVotes.json": {
content: JSON.stringify(averageCorrectVotes)
}
}
};
const headers = {
"Accept": "application/vnd.github+json",
"Authorization": `Bearer ${token}`,
"X-GitHub-Api-Version": "2022-11-28"
};
fetch('https://api.github.com/gists', {
method: 'POST',
headers: headers,
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => console.log('Gist created:', data.html_url))
.catch(error => console.error('Error creating Gist:', error));
}
const getCorrectVotesPercentage = (game: Game, playerId: GameId<'players'>, llms: Player[] ) => {
const playerVotes = game.world.llmVotes.filter((vote) => vote.voter === playerId);
if (playerVotes.length === 0) {
return 0;
}
const correctVotes = playerVotes[0].playerIds.filter((id) => llms.map((llm) => llm.id).includes(id));
return correctVotes.length / llms.length;
}
export const gameCycleSchema = {
currentTime: v.number(),
cycleState: v.union(
v.literal('Day'),
v.literal('Night'),
v.literal('WerewolfVoting'),
v.literal('PlayerKillVoting'),
v.literal('EndGame'),
v.literal('LobbyState'),
),
cycleIndex: v.number(),
cycleNumber: v.number(),
};
export type SerializedGameCycle = ObjectType<typeof gameCycleSchema>;
const onStateChange = (prevState: CycleState, newState: CycleState, game: Game, now: number) => {
console.log(`state changed: ${ prevState } -> ${ newState }`);
console.log("newState is :",newState)
if(newState ==="WerewolfVoting"){
const allVillagers = [...game.world.players.values()]
const villagers = [...game.playerDescriptions.values()].filter(player =>
player.type === 'villager'
);
// TODO: You should't vote for yourelf
allVillagers.forEach((villager) => {
LLmvotingCallWerewolf(villager, villagers).then(result => {
parseLLMVotingResult(villager, result, game)
})
})
};
if(newState ==="PlayerKillVoting"){
const werewolves = [...game.world.players.values()].filter((were) => {
game.playerDescriptions.get(were.id)?.type === 'werewolf'
})
const villagers = [...game.playerDescriptions.values()]
werewolves.forEach((were) => {
LLmvotingCallWerewolf(were, villagers).then(result => {
parseLLMVotingResult(were, result, game)
})
})
};
if (prevState === 'PlayerKillVoting') {
// const werewolves = [...game.world.players.values()].filter((were) => {
// game.playerDescriptions.get(were.id)?.type === 'werewolf'
// })
// if (werewolves.length != 0) {
const mostVotedPlayer = processVotes(game.world.gameVotes, [...game.world.players.values()])[0];
const playerToKill = game.world.players.get(mostVotedPlayer.playerId);
console.log(`killing: ${playerToKill?.id}, with ${game.world.gameVotes.length} votes`)
if (playerToKill) {
playerToKill.kill(game, now);
}
// } else {
// console.log('no werewolves, nobody was killed')
// }
game.world.gameVotes = [];
}
if (prevState === 'WerewolfVoting') {
const mostVotedPlayer = processVotes(game.world.gameVotes, [...game.world.players.values()])[0];
const suspect = game.world.players.get(mostVotedPlayer.playerId);
console.log(`suspect: ${suspect?.id}, with ${game.world.gameVotes.length} votes`)
if (suspect?.playerType(game) === 'werewolf') {
suspect?.kill(game, now)
}
game.world.gameVotes = [];
}
if (newState === 'EndGame') {
const llms = [...game.world.playersInit.values()].filter((player) => {
!player.human
})
const averageCorrectVotes = game.world.llmVotes.map((votes) => {
return getCorrectVotesPercentage(game, votes.voter, llms);
})
// Push to gist
pushToGist(averageCorrectVotes);
}
if (prevState == 'LobbyState') {
game.assignRoles()
}
};
export class GameCycle {
currentTime: number;
cycleState: CycleState;
cycleIndex: number;
cycleNumber:number;
constructor(serialized: SerializedGameCycle) {
const { currentTime, cycleState, cycleIndex,cycleNumber} = serialized;
this.currentTime = currentTime;
this.cycleState = cycleState;
this.cycleIndex = cycleIndex;
this.cycleNumber=cycleNumber;
}
startGame(game: Game) {
this.currentTime = 0;
onStateChange(this.cycleState, 'Day', game, 0);
this.cycleState = 'Day';
this.cycleIndex = 0;
this.cycleNumber=0;
console.log('Game started')
}
endgame(game: Game) {
this.currentTime = 0;
onStateChange(this.cycleState, 'EndGame', game, 0);
this.cycleState = 'EndGame';
this.cycleIndex = -1;
console.log('EndGame reached')
}
// Tick method to increment the counter
tick(game: Game, tickDuration: number) {
console.log(process.env.GITHUB_TOKEN)
this.currentTime += tickDuration;
if (this.currentTime >= stateDurations[this.cycleState]) {
const prevState = this.cycleState;
this.currentTime = 0;
this.cycleIndex = (this.cycleIndex + 1) % normalCycle.length;
this.cycleNumber = Math. ceil((this.cycleNumber + 1) / normalCycle.length);
this.cycleState = normalCycle[this.cycleIndex];
onStateChange(prevState, this.cycleState, game, tickDuration);
}
}
serialize(): SerializedGameCycle {
const { currentTime, cycleState, cycleIndex , cycleNumber} = this;
return {
currentTime,
cycleState,
cycleIndex,
cycleNumber,
};
}
}