Conversation
| [3, 4, 4, 5, 5], | ||
| [3, 4, 4, 5, 5], | ||
| [3, 4, 4, 5, 5], | ||
| ]; |
There was a problem hiding this comment.
Could you keep this here but instead conditionally use the sinad versions in the pickingTeam phase?
There was a problem hiding this comment.
Hmm, alternatively, if you want to have the GUI update live, you can directly mutate this array when you need to. Const arrays can still be modified.
There was a problem hiding this comment.
What about keeping two global arrays, and having a reference to one of the two arrays as a field of the Game class? updateMissionSizesForSinad() can then update that reference to point to the other array.
There was a problem hiding this comment.
Hmmm, actually, something I didn't think about was that this const object is shared across all games, so modifying it directly will cause issues. We'll need to either do what you did and move it onto the Game object itself as a member, or we'll have to make a static function that is given sinadMode and returns one or the other.
I might slightly prefer your current solution. Let me think a bit :)
| } | ||
| if (this.enableSinadMode) { | ||
| this.sendText('The game has Sinad Mode enabled.', 'gameplay-text'); | ||
| } |
There was a problem hiding this comment.
Can you also check that enableSinadMode can only be true if it's 6P here?
https://github.com/vck3000/ProAvalon/blob/master/src/gameplay/game.ts#L362-L378
This way if the host misconfigures the game, the server will tell them when they try to start the game.
There was a problem hiding this comment.
My current solution to this.
I wanted to make this check inside static checkOptions(options: string[]), but I wasn't sure because it looks like the options array only has Role enum members, and I think startGame(options: string[]) relies on this. Alternatively, I could try making checkOptions non-static to access this.enableSinadMode directly, but looks tricky.
There was a problem hiding this comment.
I see. Feel free to make that non-static so you can put it together.
The design of Room and Game is pretty poor. Ideally these kinds of checks are done within the Game.
| return []; | ||
| } | ||
|
|
||
| updateMissionSizesForSinad(): void { |
There was a problem hiding this comment.
The check for Sinad can actually be done as a separate system, but it's not currently a thing.
https://github.com/vck3000/ProAvalon/blob/master/src/gameplay/game.ts#L758-L809
See this section^. Essentially, every role and card gets an opportunity to move the game into a different phase/state after each phase has acted in this function call: checkRoleCardSpecialMoves().
We can generalise this behavior to non-role and non-card specific actions such as a game setting like Sinad. If you're interested we can pursue this. Alternatively, happy for you to leave this implementation here for now.
| this.thisRoom.missionHistory[1] === 'succeeded' && | ||
| this.thisRoom.missionHistory[2] === 'failed' && | ||
| this.thisRoom.missionNum === 4 && | ||
| this.thisRoom.pickNum === 1 |
There was a problem hiding this comment.
We should have a separate boolean tracking whether this has activated. This will help us avoid duplicate runs into this function. Although this code is only reached when a mission has voted, it'd be simpler and safer to just check for m1 success, m2 success, m3 fail, and then sinad hasn't activated yet to enter this function.
There was a problem hiding this comment.
Added a new method and field to track this. Hope this works.
| for (const player in VH) { | ||
| if (VH.hasOwnProperty(player)) { | ||
|
|
||
| const playerVH = VH[player]; | ||
| const playerM2 = playerVH[1][playerVH[1].length -1]; | ||
| const playerM3 = playerVH[2][playerVH[2].length -1]; | ||
|
|
||
| if (typeof playerM2 === 'string' && playerM2.includes("VHpicked")) { | ||
| setOfPlayersOnM2.add(player); | ||
| } | ||
| if (typeof playerM3 === 'string' && playerM3.includes("VHpicked")) { | ||
| setOfPlayersOnM3.add(player); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| let isM2ASubsetOfM3: boolean = [...setOfPlayersOnM2].every(player => setOfPlayersOnM3.has(player)); |
There was a problem hiding this comment.
Let's pull this code out to another file. Let's also add some tests.
E.g. I quickly spun this up. Should be good reference :).
#714
Note I cheated a bit and used a node v22 feature (Set.difference()). Please find another solution for this, like the one you have here.
There was a problem hiding this comment.
Wrote function isSubsetOf(setA, setB) in game.ts. Test case passes.
…need to code sinad mode logic.
- Split above func into getPlayersOnMission and subsetOf. - Room now prevents starting sinad mode without 6p. - Test cases are WIP. - rename NUMS_PLAYER_ON_MISSION to camelCase
- bug fix in isSubsetOf func - add more sinad testcases for new funcs.
- refactor sinad test cases - more isSubsetOf test cases - cleanup comments - better formatting - BUILD ERROR. DO NOT MERGE.
| game.voteHistory = vh; | ||
| game.enableSinadMode = true; | ||
| game.playersInGame = ['Piplup', 'Torchic', 'Mudkip', 'Turtwig', 'MewTwo', 'Charmander']; | ||
| game.gameMode = GameMode.AVALON; | ||
| game.missionHistory = ['succeeded','succeeded','failed']; |
There was a problem hiding this comment.
Build error:
Property 'voteHistory' does not exist on type 'Game'.
Property 'enableSinadMode' does not exist on type 'Game'.
Not sure why this would happen, since the other fields (missionHistory, playersInGame) did not throw this error.
What's weirder is, $ yarn test works perfectly fine with this. For instance if you change game.enableSinadMode to false here, then yarn test will throw an error.
| this.enableSinadMode = gameConfig.enableSinadMode; | ||
| this.roomCreationType = gameConfig.roomCreationType; | ||
| this.getTimeFunc = gameConfig.getTimeFunc; | ||
|
|
|
|
||
| getPlayersOnMission(missionNum: number): Set<string> { | ||
| const set = new Set<string>(); | ||
| const VH = this.voteHistory; //brevity |
There was a problem hiding this comment.
| const VH = this.voteHistory; //brevity | |
| const vh = this.voteHistory; |
| } | ||
|
|
||
| getPlayersOnMission(missionNum: number): Set<string> { | ||
| const set = new Set<string>(); |
There was a problem hiding this comment.
| const set = new Set<string>(); | |
| const playersOnMission = new Set<string>(); |
| const VH = this.voteHistory; //brevity | ||
|
|
||
| if(VH === undefined) { | ||
| return set; // empty set. |
| } | ||
|
|
||
| for (const player in VH) { | ||
| //TODO: write check to ensure player really is in this.PlayersInGame. |
There was a problem hiding this comment.
It's ok, we can assume that the players in VH are actual players in the game.
Can remove this comment.
|
|
||
| for (const player in VH) { | ||
| //TODO: write check to ensure player really is in this.PlayersInGame. | ||
| if (VH.hasOwnProperty(player)) { |
| //TODO: write check to ensure player really is in this.PlayersInGame. | ||
| if (VH.hasOwnProperty(player)) { | ||
| if(VH[player].length < missionNum) { | ||
| //in case mission hasn't happened yet. |
There was a problem hiding this comment.
| //in case mission hasn't happened yet. | |
| // If the mission hasn't happened yet. |
| return set; | ||
| } | ||
|
|
||
| const missionNumVH = VH[player][missionNum - 1]; |
There was a problem hiding this comment.
| const missionNumVH = VH[player][missionNum - 1]; | |
| const missionVh = VH[player][missionNum - 1]; |
| return newRating; | ||
| } | ||
|
|
||
| getPlayersOnMission(missionNum: number): Set<string> { |
There was a problem hiding this comment.
| getPlayersOnMission(missionNum: number): Set<string> { | |
| getPlayersWhoWentOnMission(missionNum: number): Set<string> { |
For some extra little bit of clarity
| this.getPlayersOnMission(2) | ||
| ,this.getPlayersOnMission(3) | ||
| )) { | ||
| this.numPlayersOnMission[6 - MIN_PLAYERS] = [2,3,4,4,3]; |
There was a problem hiding this comment.
Can you run prettier to format the file pls?
https://prettier.io/docs/en/cli.html
It should be installed when you run yarn, but if not, do yarn prettier . --write
| console.log(this.getPlayersOnMission(2)) ; | ||
| console.log(this.getPlayersOnMission(3)) ; | ||
| console.log(!isSubsetOf(this.getPlayersOnMission(2),this.getPlayersOnMission(3))); |
There was a problem hiding this comment.
Please clear these guys out when you're done debugging.
| if(!this.thisRoom.hasSinadRun && this.thisRoom.shouldSinadRun()) { | ||
| this.thisRoom.updateMissionSizesSinad(); |
There was a problem hiding this comment.
Can you rename this to this.thisRoom.checkSinad(), and then move the hasSinadRun check, and shouldSinadRun check into checkSinad()?
That way it's a single entrypoint, making it easier for clients to use.
There was a problem hiding this comment.
Then you can remove this.thisRoom.hasSinadRun = true; below as well
| this.hasSinadRun = true; | ||
| } | ||
|
|
||
| hasSinadRun: boolean = false; |
There was a problem hiding this comment.
Can you move this to the top of this class with the other members
| return rolesAssignment; | ||
| } | ||
|
|
||
| export function isSubsetOf(setA, setB): boolean { |
There was a problem hiding this comment.
Can you provide the types here for setA and setB please
| return newRating; | ||
| } | ||
|
|
||
| getPlayersOnMission(missionNum: number): Set<string> { |
There was a problem hiding this comment.
Can you make this method private please. Outside code shouldn't call into this.
| expect(mockGame.shouldSinadRun()).toEqual(false); | ||
| }); | ||
|
|
||
| it('should not run if m1 failed', () => { |
There was a problem hiding this comment.
Can you move these tests to the existing game.test.ts?
That way you don't have to use mocks and you can get better test coverage.
| it('should pass these Sets declared the old fashioned way', () => { | ||
| const setA = new Set<string>(['a', 'b', 'c']); | ||
| const setB = new Set<string>(['a', 'd', 'c','b']); | ||
| expect(isSubsetOf(setA,setB)).toEqual(true); | ||
| const setC = new Set<string>(['a','e','d','c']); | ||
| expect(isSubsetOf(setA,setC)).toEqual(false); | ||
| expect(isSubsetOf(undefined,setC)).toEqual(false); | ||
| expect(isSubsetOf(setA,undefined)).toEqual(false); | ||
| expect(isSubsetOf(new Set<String>(),undefined)).toEqual(false); | ||
| expect(isSubsetOf(undefined,new Set<String>())).toEqual(false); | ||
| expect(isSubsetOf(undefined, undefined)).toEqual(false); | ||
|
|
||
| }); |
There was a problem hiding this comment.
We should test these through the Game's API (i.e. at a higher level) in game.test.ts.
If you want to test isSubsetOf() specifically like you've done here, please split that function to a separate file, and move these tests to just isSubsetOf.test.ts or similar.
| } | ||
| } | ||
|
|
||
| function updateRoomEnableSinadMode(enableSinadMode) { |
There was a problem hiding this comment.
| function updateRoomEnableSinadMode(enableSinadMode) { | |
| function updateRoomEnableSinadMode(enableSinadMode: boolean) { |
| numFailsHistory: [Number], | ||
| voteHistory: Object, | ||
| disableVoteHistory: Boolean, | ||
| enableSinadMode: Boolean, |
There was a problem hiding this comment.
Did you add this to the game finish method in game.ts?
This pull-request introduces Sinad mode, a toggle-able setting in the menu that modifies the mission sizes of 6 player Avalon. If mission 1 and 2 both succeed, and mission 3 fails, and if mission 3 was not a superset of m2, (i.e. m3!=m2+1)
The mission size for Mission 4 becomes a 4-man team, and the final mission 5 becomes a 3-man team.
The author hopes that this mode will make 6p games more interesting.