-
Notifications
You must be signed in to change notification settings - Fork 74
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
Sinadmode #713
base: master
Are you sure you want to change the base?
Sinadmode #713
Conversation
[3, 4, 4, 5, 5], | ||
[3, 4, 4, 5, 5], | ||
[3, 4, 4, 5, 5], | ||
]; |
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.
Could you keep this here but instead conditionally use the sinad versions in the pickingTeam phase?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 :)
@@ -566,6 +570,9 @@ class Game extends Room { | |||
if (this.disableVoteHistory) { | |||
this.sendText('The game has vote history disabled.', 'gameplay-text'); | |||
} | |||
if (this.enableSinadMode) { | |||
this.sendText('The game has Sinad Mode enabled.', 'gameplay-text'); | |||
} |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -270,6 +275,61 @@ class VotingMission implements IPhase { | |||
getProhibitedIndexesToPick(indexOfPlayer: number): number[] { | |||
return []; | |||
} | |||
|
|||
updateMissionSizesForSinad(): void { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.roomCreationType = gameConfig.roomCreationType; | ||
this.getTimeFunc = gameConfig.getTimeFunc; | ||
|
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.
Can you add this back in
|
||
getPlayersOnMission(missionNum: number): Set<string> { | ||
const set = new Set<string>(); | ||
const VH = this.voteHistory; //brevity |
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.
const VH = this.voteHistory; //brevity | |
const vh = this.voteHistory; |
@@ -2283,8 +2300,73 @@ class Game extends Room { | |||
} | |||
return newRating; | |||
} | |||
|
|||
getPlayersOnMission(missionNum: number): Set<string> { | |||
const set = new Set<string>(); |
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.
const set = new Set<string>(); | |
const playersOnMission = new Set<string>(); |
const VH = this.voteHistory; //brevity | ||
|
||
if(VH === undefined) { | ||
return set; // empty set. |
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.
Don't need comment
} | ||
|
||
for (const player in VH) { | ||
//TODO: write check to ensure player really is in this.PlayersInGame. |
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.
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)) { |
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.
Did you need this if statement?
//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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const missionNumVH = VH[player][missionNum - 1]; | |
const missionVh = VH[player][missionNum - 1]; |
@@ -2283,8 +2300,73 @@ class Game extends Room { | |||
} | |||
return newRating; | |||
} | |||
|
|||
getPlayersOnMission(missionNum: number): Set<string> { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then you can remove this.thisRoom.hasSinadRun = true;
below as well
this.hasSinadRun = true; | ||
} | ||
|
||
hasSinadRun: boolean = false; |
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.
Can you move this to the top of this class with the other members
@@ -2310,6 +2392,20 @@ function generateAssignmentOrders(num) { | |||
return rolesAssignment; | |||
} | |||
|
|||
export function isSubsetOf(setA, setB): boolean { |
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.
Can you provide the types here for setA and setB please
@@ -2283,8 +2300,73 @@ class Game extends Room { | |||
} | |||
return newRating; | |||
} | |||
|
|||
getPlayersOnMission(missionNum: number): Set<string> { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -1933,6 +1939,14 @@ function updateRoomDisableVoteHistory(disableVoteHistory) { | |||
} | |||
} | |||
|
|||
function updateRoomEnableSinadMode(enableSinadMode) { |
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.
function updateRoomEnableSinadMode(enableSinadMode) { | |
function updateRoomEnableSinadMode(enableSinadMode: boolean) { |
@@ -41,6 +41,7 @@ const gameRecordSchema = new mongoose.Schema({ | |||
numFailsHistory: [Number], | |||
voteHistory: Object, | |||
disableVoteHistory: Boolean, | |||
enableSinadMode: Boolean, |
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.
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.