-
Notifications
You must be signed in to change notification settings - Fork 78
Melron and Moregano #745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Melron and Moregano #745
Changes from all commits
0874a66
fe911b4
3de4c3c
d7d9948
c944f81
ac70510
10caa78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,8 @@ import usernamesIndexes from '../../../../myFunctions/usernamesIndexes'; | |
| import { ButtonSettings, IPhase, Phase } from '../types'; | ||
| import { Alliance } from '../../types'; | ||
| import { SocketUser } from '../../../../sockets/types'; | ||
| import { Role } from '../../roles/types'; | ||
|
|
||
|
|
||
| class VotingMission implements IPhase { | ||
| static phase = Phase.VotingMission; | ||
|
|
@@ -32,22 +34,39 @@ class VotingMission implements IPhase { | |
| ) | ||
| ] = 'succeed'; | ||
| // console.log("received succeed from " + socket.request.user.username); | ||
| } else if (buttonPressed === 'no') { | ||
| // If the user is a res, they shouldn't be allowed to fail | ||
| const index = usernamesIndexes.getIndexFromUsername( | ||
| this.thisRoom.playersInGame, | ||
| socket.request.user.username, | ||
| ); | ||
| if ( | ||
| index !== -1 && | ||
| this.thisRoom.playersInGame[index].alliance === Alliance.Resistance | ||
| ) { | ||
| socket.emit( | ||
| 'danger-alert', | ||
| 'You are resistance! Surely you want to succeed!', | ||
| ); | ||
| return; | ||
| } | ||
| } else if (buttonPressed === 'no') { | ||
| // Determine the player index | ||
| const index = usernamesIndexes.getIndexFromUsername( | ||
| this.thisRoom.playersInGame, | ||
| socket.request.user.username, | ||
| ); | ||
|
|
||
| // If player is Resistance and NOT Moregano, block failing | ||
| if ( | ||
| index !== -1 && | ||
| this.thisRoom.playersInGame[index].alliance === Alliance.Resistance && | ||
| this.thisRoom.playersInGame[index].role !== Role.Moregano | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm, will need to check whether this will subtly break the timer. This code path is actually now dead and should never happen, as the buttonsAvailable should never show fail as an option if you're res. I'll have a think about how to handle this. Edit: This won't be an issue if |
||
| ) { | ||
| socket.emit( | ||
| 'danger-alert', | ||
| 'You are resistance! Surely you want to succeed!', | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| // If player is Moregano and pressed "no", silently record "succeed". | ||
| const effectiveVote = | ||
| index !== -1 && this.thisRoom.playersInGame[index].role === Role.Moregano | ||
| ? 'succeed' | ||
| : 'fail'; | ||
|
|
||
| this.thisRoom.missionVotes[ | ||
| usernamesIndexes.getIndexFromUsername( | ||
| this.thisRoom.playersInGame, | ||
| socket.request.user.username, | ||
| ) | ||
| ] = effectiveVote; | ||
|
|
||
|
|
||
| this.thisRoom.missionVotes[ | ||
| usernamesIndexes.getIndexFromUsername( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { Alliance, See } from '../../types'; | ||
| import { IRole, Role } from '../types'; | ||
| import Game from '../../game'; | ||
|
|
||
| /** | ||
| * Melron (Resistance) — believes they are Merlin. | ||
| * Sees a RANDOM set of players as spies: size mirrors Merlin’s count | ||
| * (real spies minus 1 if Mordred/MordredAssassin is in play). | ||
| * Percival does NOT see Melron. | ||
| */ | ||
| class Melron implements IRole { | ||
| room: Game; | ||
|
|
||
| static role = Role.Melron; | ||
| role = Role.Melron; | ||
|
|
||
| alliance = Alliance.Resistance; | ||
|
|
||
| description = 'Thinks they are Merlin; sees a random “spy” list mirroring Merlin’s count.'; | ||
| orderPriorityInOptions = 75; // place near Percival/Morgana if you care about ordering | ||
|
|
||
| specialPhase: string; | ||
|
|
||
| constructor(thisRoom: any) { | ||
| this.room = thisRoom; | ||
| } | ||
|
|
||
| see(): See { | ||
| const spies: string[] = []; | ||
| if (!this.room.gameStarted) return { spies, roleTags: {} }; | ||
|
|
||
| // Count real spies and detect Mordred/MordredAssassin (Merlin doesn’t see them) | ||
| let realSpyCount = 0; | ||
| let hasMordred = false; | ||
|
|
||
| for (let i = 0; i < this.room.playersInGame.length; i++) { | ||
| const p = this.room.playersInGame[i]; | ||
| if (p.alliance === Alliance.Spy) { | ||
| realSpyCount++; | ||
| if (p.role === Role.Mordred || p.role === Role.MordredAssassin) { | ||
| hasMordred = true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const k = Math.max(0, realSpyCount - (hasMordred ? 1 : 0)); | ||
|
|
||
| // Build pool of all non-self usernames | ||
| const self = this.getSelfUsername(); | ||
| const pool = this.room.playersInGame | ||
| .map((p: any) => p.username) | ||
| .filter((u: string) => u !== self); | ||
|
|
||
| // Shuffle pool (Fisher–Yates) and pick k | ||
| for (let i = pool.length - 1; i > 0; i--) { | ||
| const j = Math.floor(Math.random() * (i + 1)); | ||
| [pool[i], pool[j]] = [pool[j], pool[i]]; | ||
| } | ||
| const picks = pool.slice(0, k); | ||
|
|
||
| for (const u of picks) spies.push(this.room.anonymizer.anon(u)); | ||
|
|
||
| return { spies, roleTags: {} }; | ||
|
Comment on lines
+48
to
+63
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe we'll need to do this on game start and remember the spies we've built up. |
||
| } | ||
|
|
||
| private getSelfUsername(): string { | ||
| for (let i = 0; i < this.room.playersInGame.length; i++) { | ||
| if (this.room.playersInGame[i].role === Role.Melron) { | ||
| return this.room.playersInGame[i].username; | ||
| } | ||
| } | ||
| return ''; | ||
| } | ||
|
|
||
| checkSpecialMove(): void {} | ||
| getPublicGameData(): any {} | ||
| } | ||
|
|
||
| export default Melron; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import { Alliance, See } from '../../types'; | ||
| import { IRole, Role } from '../types'; | ||
| import Game from '../../game'; | ||
|
|
||
| /** | ||
| * Moregano (Resistance) — believes they are Morgana. | ||
| * Sees a FAKE spy team that MUST include self; size mirrors Morgana’s count | ||
| * (real spies minus Oberon). | ||
| * If they press "Fail", it is silently processed as "Succeed". | ||
| */ | ||
| class Moregano implements IRole { | ||
| room: Game; | ||
|
|
||
| static role = Role.Moregano; | ||
| role = Role.Moregano; | ||
|
|
||
| alliance = Alliance.Resistance; | ||
|
|
||
| description = 'Thinks they are Morgana; sees a fake spy team; their Fail counts as Success.'; | ||
| orderPriorityInOptions = 72; | ||
|
|
||
| specialPhase: string; | ||
|
|
||
| constructor(thisRoom: any) { | ||
| this.room = thisRoom; | ||
| } | ||
|
|
||
| see(): See { | ||
| const spies: string[] = []; | ||
| if (!this.room.gameStarted) return { spies, roleTags: {} }; | ||
|
|
||
| // Count real spies and detect Oberon (Morgana doesn’t see Oberon) | ||
| let realSpyCount = 0; | ||
| let hasOberon = false; | ||
|
|
||
| for (let i = 0; i < this.room.playersInGame.length; i++) { | ||
| const p = this.room.playersInGame[i]; | ||
| if (p.alliance === Alliance.Spy) { | ||
| realSpyCount++; | ||
| if (p.role === Role.Oberon) hasOberon = true; | ||
| } | ||
| } | ||
|
|
||
| const k = Math.max(1, realSpyCount - (hasOberon ? 1 : 0)); // include self, so at least 1 | ||
|
|
||
| const self = this.getSelfUsername(); | ||
| if (!self) return { spies, roleTags: {} }; | ||
|
|
||
| // Start with self | ||
| spies.push(this.room.anonymizer.anon(self)); | ||
|
|
||
| const othersNeeded = k - 1; | ||
| if (othersNeeded <= 0) return { spies, roleTags: {} }; | ||
|
|
||
| // Pool of non-self usernames | ||
| const pool = this.room.playersInGame | ||
| .map((p: any) => p.username) | ||
| .filter((u: string) => u !== self); | ||
|
|
||
| // Shuffle pool and pick othersNeeded | ||
| for (let i = pool.length - 1; i > 0; i--) { | ||
| const j = Math.floor(Math.random() * (i + 1)); | ||
| [pool[i], pool[j]] = [pool[j], pool[i]]; | ||
| } | ||
| const picks = pool.slice(0, othersNeeded); | ||
|
|
||
| for (const u of picks) spies.push(this.room.anonymizer.anon(u)); | ||
|
|
||
| return { spies, roleTags: {} }; | ||
| } | ||
|
|
||
| private getSelfUsername(): string { | ||
| for (let i = 0; i < this.room.playersInGame.length; i++) { | ||
| if (this.room.playersInGame[i].role === Role.Moregano) { | ||
| return this.room.playersInGame[i].username; | ||
| } | ||
| } | ||
| return ''; | ||
| } | ||
|
|
||
| checkSpecialMove(): void {} | ||
| getPublicGameData(): any {} | ||
| } | ||
|
|
||
| export default Moregano; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indenting is a bit off.
Will need to confirm if .displayRole is the correct thing to be overriding.