Skip to content

Commit 0449e33

Browse files
CopilotAlecM33
andauthored
Add independent alignment for custom roles (#217)
* Initial plan * Add independent alignment: constants, validation, UI, and CSS Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Add unit tests for independent alignment validation Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Fix remaining binary alignment checks in role display Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Add independent player container and game-role-independent CSS Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * fix a couple bugs, tweak colors * remove import * remove duplicate tag --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> Co-authored-by: AlecM33 <leohfx@gmail.com>
1 parent b328bff commit 0449e33

File tree

13 files changed

+203
-54
lines changed

13 files changed

+203
-54
lines changed

client/src/config/globals.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export const STATUS = {
2121

2222
export const ALIGNMENT = {
2323
GOOD: 'good',
24-
EVIL: 'evil'
24+
EVIL: 'evil',
25+
INDEPENDENT: 'independent'
2526
};
2627

2728
export const MESSAGES = {

client/src/modules/front_end_components/HTMLFragments.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,15 @@ export const HTMLFragments = {
194194
<label id='players-alive-label'></label>
195195
<div id="spectator-count" tabindex="0"></div>
196196
<div id='game-player-list'>
197-
<div class='evil-players'>
197+
<div id="independent-players" class='independent-players' style="display: none;">
198+
<label class='independent'>Team Independent</label>
199+
<div id='player-list-moderator-team-independent'></div>
200+
</div>
201+
<div id="evil-players" class='evil-players' style="display: none;">
198202
<label class='evil'>Team Evil</label>
199203
<div id='player-list-moderator-team-evil'></div>
200204
</div>
201-
<div class='good-players'>
205+
<div id="good-players" class='good-players' style="display: none;">
202206
<label class='good'>Team Good</label>
203207
<div id='player-list-moderator-team-good'></div>
204208
</div>

client/src/modules/game_creation/DeckStateManager.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ export class DeckStateManager {
147147
}
148148
const sortedDeck = this.deck.sort((a, b) => {
149149
if (a.team !== b.team) {
150-
return a.team === ALIGNMENT.GOOD ? -1 : 1;
150+
const order = { good: 0, evil: 1, independent: 2 };
151+
return order[a.team] - order[b.team];
151152
}
152153
return a.role.localeCompare(b.role);
153154
});
@@ -187,11 +188,7 @@ export class DeckStateManager {
187188
roleEl.dataset.roleId = sortedDeck[i].id;
188189
roleEl.classList.add('added-role');
189190
roleEl.innerHTML = HTMLFragments.DECK_SELECT_ROLE_ADDED_TO_DECK;
190-
if (sortedDeck[i].team === ALIGNMENT.GOOD) {
191-
roleEl.classList.add(ALIGNMENT.GOOD);
192-
} else {
193-
roleEl.classList.add(ALIGNMENT.EVIL);
194-
}
191+
roleEl.classList.add(sortedDeck[i].team);
195192
populateRoleElementInfo(roleEl, sortedDeck, i);
196193
document.getElementById('deck-list').appendChild(roleEl);
197194
const minusOneHandler = (e) => {
@@ -237,8 +234,10 @@ export class DeckStateManager {
237234
const nameEl = document.getElementById('custom-role-info-modal-name');
238235
alignmentEl.classList.remove(ALIGNMENT.GOOD);
239236
alignmentEl.classList.remove(ALIGNMENT.EVIL);
237+
alignmentEl.classList.remove(ALIGNMENT.INDEPENDENT);
240238
nameEl.classList.remove(ALIGNMENT.GOOD);
241239
nameEl.classList.remove(ALIGNMENT.EVIL);
240+
nameEl.classList.remove(ALIGNMENT.INDEPENDENT);
242241
e.preventDefault();
243242
nameEl.innerText = sortedDeck[i].role;
244243
nameEl.classList.add(sortedDeck[i].team);
@@ -256,6 +255,7 @@ export class DeckStateManager {
256255
function populateRoleElementInfo (roleEl, sortedDeck, i) {
257256
roleEl.classList.remove(ALIGNMENT.GOOD);
258257
roleEl.classList.remove(ALIGNMENT.EVIL);
258+
roleEl.classList.remove(ALIGNMENT.INDEPENDENT);
259259
roleEl.classList.add(sortedDeck[i].team);
260260
roleEl.querySelector('.role-name').innerHTML =
261261
`<span class="role-quantity"></span>

client/src/modules/game_creation/GameCreationStepManager.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -463,11 +463,7 @@ function renderReviewAndCreateStep (containerId, stepNumber, game, deckManager)
463463
for (const card of game.deck) {
464464
const roleEl = document.createElement('div');
465465
roleEl.innerText = card.quantity + 'x ' + card.role;
466-
if (card.team === ALIGNMENT.GOOD) {
467-
roleEl.classList.add(ALIGNMENT.GOOD);
468-
} else {
469-
roleEl.classList.add(ALIGNMENT.EVIL);
470-
}
466+
roleEl.classList.add(card.team);
471467
div.querySelector('#roles-option').appendChild(roleEl);
472468
}
473469

client/src/modules/game_creation/RoleBox.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export class RoleBox {
3131
loadDefaultRoles = () => {
3232
this.defaultRoles = defaultRoles.sort((a, b) => {
3333
if (a.team !== b.team) {
34-
return a.team === ALIGNMENT.GOOD ? -1 : 1;
34+
const order = { good: 0, evil: 1, independent: 2 };
35+
return order[a.team] - order[b.team];
3536
}
3637
return a.role.localeCompare(b.role);
3738
}).map((role) => {
@@ -174,10 +175,7 @@ export class RoleBox {
174175
defaultRole.innerHTML = HTMLFragments.DECK_SELECT_ROLE_DEFAULT;
175176
defaultRole.classList.add('default-role');
176177
defaultRole.dataset.roleId = this.defaultRoles[i].id;
177-
const alignmentClass = this.defaultRoles[i].team === ALIGNMENT.GOOD
178-
? ALIGNMENT.GOOD
179-
: ALIGNMENT.EVIL;
180-
defaultRole.classList.add(alignmentClass);
178+
defaultRole.classList.add(this.defaultRoles[i].team);
181179
defaultRole.querySelector('.role-name').innerText = this.defaultRoles[i].role;
182180
selectEl.appendChild(defaultRole);
183181
}
@@ -202,7 +200,8 @@ export class RoleBox {
202200
}
203201
this.customRoles.sort((a, b) => {
204202
if (a.team !== b.team) {
205-
return a.team === ALIGNMENT.GOOD ? -1 : 1;
203+
const order = { good: 0, evil: 1, independent: 2 };
204+
return order[a.team] - order[b.team];
206205
}
207206
return a.role.localeCompare(b.role);
208207
});
@@ -212,8 +211,7 @@ export class RoleBox {
212211
customRole.innerHTML = HTMLFragments.DECK_SELECT_ROLE;
213212
customRole.classList.add('custom-role');
214213
customRole.dataset.roleId = this.customRoles[i].id;
215-
const alignmentClass = this.customRoles[i].team === ALIGNMENT.GOOD ? ALIGNMENT.GOOD : ALIGNMENT.EVIL;
216-
customRole.classList.add(alignmentClass);
214+
customRole.classList.add(this.customRoles[i].team);
217215
customRole.querySelector('.role-name').innerText = this.customRoles[i].role;
218216
selectEl.appendChild(customRole);
219217
}
@@ -282,8 +280,10 @@ export class RoleBox {
282280
const nameEl = document.getElementById('custom-role-info-modal-name');
283281
alignmentEl.classList.remove(ALIGNMENT.GOOD);
284282
alignmentEl.classList.remove(ALIGNMENT.EVIL);
283+
alignmentEl.classList.remove(ALIGNMENT.INDEPENDENT);
285284
nameEl.classList.remove(ALIGNMENT.GOOD);
286285
nameEl.classList.remove(ALIGNMENT.EVIL);
286+
nameEl.classList.remove(ALIGNMENT.INDEPENDENT);
287287
e.preventDefault();
288288
let role;
289289
if (isCustom) {
@@ -391,7 +391,7 @@ function validateCustomRoleCookie (cookie) {
391391
for (const entry of cookieJSON) {
392392
if (entry !== null && typeof entry === 'object') {
393393
if (typeof entry.role !== 'string' || entry.role.length > PRIMITIVES.MAX_CUSTOM_ROLE_NAME_LENGTH
394-
|| typeof entry.team !== 'string' || (entry.team !== ALIGNMENT.GOOD && entry.team !== ALIGNMENT.EVIL)
394+
|| typeof entry.team !== 'string' || (entry.team !== ALIGNMENT.GOOD && entry.team !== ALIGNMENT.EVIL && entry.team !== ALIGNMENT.INDEPENDENT)
395395
|| typeof entry.description !== 'string' || entry.description.length > PRIMITIVES.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH
396396
) {
397397
return false;

client/src/modules/game_state/states/InProgress.js

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -295,26 +295,49 @@ export class InProgress {
295295
&& ((p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR)
296296
|| p.killed)
297297
);
298-
this.renderGroupOfPlayers(
299-
teamEvil,
300-
this.killPlayerHandlers,
301-
this.revealRoleHandlers,
302-
this.stateBucket.currentGameState.accessCode,
303-
ALIGNMENT.EVIL,
304-
this.stateBucket.currentGameState.people.find(person =>
305-
person.id === this.stateBucket.currentGameState.currentModeratorId).userType,
306-
this.socket
307-
);
308-
this.renderGroupOfPlayers(
309-
teamGood,
310-
this.killPlayerHandlers,
311-
this.revealRoleHandlers,
312-
this.stateBucket.currentGameState.accessCode,
313-
ALIGNMENT.GOOD,
314-
this.stateBucket.currentGameState.people.find(person =>
315-
person.id === this.stateBucket.currentGameState.currentModeratorId).userType,
316-
this.socket
298+
const teamIndependent = this.stateBucket.currentGameState.people.filter((p) => p.alignment === ALIGNMENT.INDEPENDENT
299+
&& ((p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR)
300+
|| p.killed)
317301
);
302+
if (teamEvil.length > 0) {
303+
document.getElementById(`${ALIGNMENT.EVIL}-players`).style.display = 'block';
304+
this.renderGroupOfPlayers(
305+
teamEvil,
306+
this.killPlayerHandlers,
307+
this.revealRoleHandlers,
308+
this.stateBucket.currentGameState.accessCode,
309+
ALIGNMENT.EVIL,
310+
this.stateBucket.currentGameState.people.find(person =>
311+
person.id === this.stateBucket.currentGameState.currentModeratorId).userType,
312+
this.socket
313+
);
314+
}
315+
if (teamGood.length > 0) {
316+
document.getElementById(`${ALIGNMENT.GOOD}-players`).style.display = 'block';
317+
this.renderGroupOfPlayers(
318+
teamGood,
319+
this.killPlayerHandlers,
320+
this.revealRoleHandlers,
321+
this.stateBucket.currentGameState.accessCode,
322+
ALIGNMENT.GOOD,
323+
this.stateBucket.currentGameState.people.find(person =>
324+
person.id === this.stateBucket.currentGameState.currentModeratorId).userType,
325+
this.socket
326+
);
327+
}
328+
if (teamIndependent.length > 0) {
329+
document.getElementById(`${ALIGNMENT.INDEPENDENT}-players`).style.display = 'block';
330+
this.renderGroupOfPlayers(
331+
teamIndependent,
332+
this.killPlayerHandlers,
333+
this.revealRoleHandlers,
334+
this.stateBucket.currentGameState.accessCode,
335+
ALIGNMENT.INDEPENDENT,
336+
this.stateBucket.currentGameState.people.find(person =>
337+
person.id === this.stateBucket.currentGameState.currentModeratorId).userType,
338+
this.socket
339+
);
340+
}
318341
document.getElementById('players-alive-label').innerText =
319342
'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' +
320343
this.stateBucket.currentGameState.gameSize + ' Alive';
@@ -460,9 +483,12 @@ function renderPlayerRole (gameState) {
460483
if (gameState.client.alignment === ALIGNMENT.GOOD) {
461484
document.getElementById('game-role').classList.add('game-role-good');
462485
name.classList.add('good');
463-
} else {
486+
} else if (gameState.client.alignment === ALIGNMENT.EVIL) {
464487
document.getElementById('game-role').classList.add('game-role-evil');
465488
name.classList.add('evil');
489+
} else if (gameState.client.alignment === ALIGNMENT.INDEPENDENT) {
490+
document.getElementById('game-role').classList.add('game-role-independent');
491+
name.classList.add('independent');
466492
}
467493
name.setAttribute('title', gameState.client.gameRole);
468494
if (gameState.client.out) {

client/src/modules/game_state/states/shared/SharedStateUtil.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import {
55
ENVIRONMENTS,
66
SOCKET_EVENTS,
77
USER_TYPE_ICONS,
8-
USER_TYPES,
9-
ALIGNMENT
8+
USER_TYPES
109
} from '../../../../config/globals.js';
1110
import { toast } from '../../../front_end_components/Toast.js';
1211
import { Confirmation } from '../../../front_end_components/Confirmation.js';
@@ -149,7 +148,11 @@ export const SharedStateUtil = {
149148
document.getElementById('role-info-button').addEventListener('click', (e) => {
150149
const deck = stateBucket.currentGameState.deck;
151150
deck.sort((a, b) => {
152-
return a.team === ALIGNMENT.GOOD ? -1 : 1;
151+
if (a.team !== b.team) {
152+
const order = { good: 0, evil: 1, independent: 2 };
153+
return order[a.team] - order[b.team];
154+
}
155+
return a.role.localeCompare(b.role);
153156
});
154157
e.preventDefault();
155158
document.getElementById('role-info-prompt').innerHTML = HTMLFragments.ROLE_INFO_MODAL;
@@ -168,11 +171,7 @@ export const SharedStateUtil = {
168171
roleName.innerText = card.role;
169172
roleQuantity.innerText = card.quantity + 'x';
170173

171-
if (card.team === ALIGNMENT.GOOD) {
172-
roleName.classList.add(ALIGNMENT.GOOD);
173-
} else {
174-
roleName.classList.add(ALIGNMENT.EVIL);
175-
}
174+
roleName.classList.add(card.team);
176175

177176
roleNameDiv.appendChild(roleQuantity);
178177
roleNameDiv.appendChild(roleName);

client/src/styles/GLOBAL.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,15 @@ input {
608608
font-weight: bold;
609609
}
610610

611+
.independent, .compact-card.independent .card-role {
612+
color: #af8523 !important;
613+
font-weight: bold;
614+
}
615+
616+
.independent-players, #deck-independent {
617+
border: 2px solid rgba(212, 160, 39, 0.39);
618+
}
619+
611620
@keyframes placeholder {
612621
0%{
613622
background-position: 50% 0

client/src/styles/game.css

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ h1 {
424424
display: none;
425425
position: relative;
426426
border: 5px solid transparent;
427-
background-color: #e3e3e3;
427+
background-color: #efefef;
428428
flex-direction: column;
429429
cursor: pointer;
430430
justify-content: space-between;
@@ -462,6 +462,10 @@ h1 {
462462
border: 4px solid #c55454 !important;
463463
}
464464

465+
.game-role-independent {
466+
border: 4px solid #af8523 !important;
467+
}
468+
465469
#game-role-back {
466470
display: flex;
467471
align-items: center;
@@ -547,6 +551,10 @@ h1 {
547551
color: #e15656 !important;
548552
}
549553

554+
#game-role #role-name.independent {
555+
color: #af8523 !important;
556+
}
557+
550558
#role-image {
551559
user-select: none;
552560
-ms-user-select: none;

client/src/view_templates/CreateTemplate.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const hiddenMenus =
1111
<select id="role-alignment" required>
1212
<option value="good">good</option>
1313
<option value="evil">evil</option>
14+
<option value="independent">independent</option>
1415
</select>
1516
</div>
1617
<div>

0 commit comments

Comments
 (0)