add bots to a game

This commit is contained in:
AlecM33
2023-01-30 12:40:28 -05:00
parent e362f6bdb2
commit 79309f5062
16 changed files with 106 additions and 43 deletions

View File

@@ -81,7 +81,8 @@ export const globals = {
PLAYER: 'player',
TEMPORARY_MODERATOR: 'temp mod',
KILLED_PLAYER: 'killed',
SPECTATOR: 'spectator'
SPECTATOR: 'spectator',
BOT: 'bot'
},
ENVIRONMENT: {
LOCAL: 'local',
@@ -92,6 +93,7 @@ export const globals = {
moderator: ' \uD83D\uDC51',
'temp mod': ' \uD83C\uDFAE\uD83D\uDC51',
spectator: ' \uD83D\uDC7B',
killed: '\uD83D\uDC80'
killed: '\uD83D\uDC80',
bot: '\uD83E\uDD16'
}
};

View File

@@ -1,9 +1,10 @@
export class Game {
constructor (deck, hasTimer, hasDedicatedModerator, moderatorName, timerParams = null) {
constructor (deck, hasTimer, hasDedicatedModerator, moderatorName, timerParams = null, isTestGame = false) {
this.deck = deck;
this.hasTimer = hasTimer;
this.timerParams = timerParams;
this.hasDedicatedModerator = hasDedicatedModerator;
this.moderatorName = moderatorName;
this.isTestGame = isTestGame;
}
}

View File

@@ -38,6 +38,13 @@ export const HTMLFragments = {
<div>
<input type="text" id="moderator-name" autocomplete='given-name' placeholder="enter your name...">
</div>
<div>
<label for="test-game">Populate the game with bots?</label>
<select id="test-game">
<option value="no" selected>No</option>
<option value="yes">Yes</option>
</select>
</div>
</div>`,
START_GAME_PROMPT:
`<div>
@@ -120,7 +127,7 @@ export const HTMLFragments = {
TRANSFER_MOD_MODAL:
`<div id='transfer-mod-modal-background' class='modal-background'></div>
<div tabindex='-1' id='transfer-mod-modal' class='modal'>
<h3>Transfer Mod Powers &#128081;</h3>
<h2>Select a new moderator &#128081;</h2>
<div id='transfer-mod-modal-content'></div>
<div class='modal-button-container'>
<button id='close-mod-transfer-modal-button' class='app-button cancel'>Cancel</button>

View File

@@ -130,7 +130,8 @@ export class GameCreationStepManager {
this.currentGame.hasTimer,
this.currentGame.hasDedicatedModerator,
this.currentGame.moderatorName,
this.currentGame.timerParams
this.currentGame.timerParams,
this.currentGame.isTestGame
)
)
)
@@ -304,6 +305,12 @@ function renderNameStep (containerId, step, game, steps) {
const nameInput = document.querySelector('#moderator-name');
nameInput.value = game.moderatorName;
nameInput.addEventListener('keyup', steps['4'].forwardHandler);
const testGameInput = document.getElementById('test-game');
testGameInput.onchange = (event) => {
game.isTestGame = testGameInput.value === 'yes';
};
testGameInput.value = game.isTestGame ? 'yes' : 'no';
}
function renderModerationTypeStep (game, containerId, stepNumber) {
@@ -390,22 +397,28 @@ function renderReviewAndCreateStep (containerId, stepNumber, game, deckManager)
div.innerHTML =
'<div>' +
"<label for='mod-name'>Your name</label>" +
"<label for='mod-name'>Your name:</label>" +
"<div id='mod-name' class='review-option'></div>" +
'</div>' +
'<div>' +
"<label for='mod-option'>Moderation</label>" +
"<label for='test-game'>Populate game with bots?</label>" +
"<div id='test-game' class='review-option'></div>" +
'</div>' +
'<div>' +
"<label for='mod-option'>Moderation:</label>" +
"<div id='mod-option' class='review-option'></div>" +
'</div>' +
'<div>' +
"<label for='timer-option'>Timer</label>" +
"<label for='timer-option'>Timer:</label>" +
"<div id='timer-option' class='review-option'></div>" +
'</div>' +
'<div>' +
"<label id='roles-option-label' for='roles-option'>Game Deck</label>" +
"<label id='roles-option-label' for='roles-option'>Game Deck:</label>" +
"<div id='roles-option' class='review-option'></div>" +
'</div>';
div.querySelector('#test-game').innerText = game.isTestGame ? 'Yes' : 'No';
div.querySelector('#mod-option').innerText = game.hasDedicatedModerator
? "Dedicated Moderator - don't deal me a card."
: 'Temporary Moderator - deal me into the game.';

View File

@@ -28,8 +28,7 @@ export class Ended {
const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null;
renderGroupOfPlayers(
this.stateBucket.currentGameState.people.filter(
p => p.userType === globals.USER_TYPES.PLAYER
|| p.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
p => (p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR)
|| p.killed
),
this.stateBucket.currentGameState.accessCode,

View File

@@ -101,8 +101,7 @@ export class InProgress {
: null;
this.renderGroupOfPlayers(
this.stateBucket.currentGameState.people.filter(
p => p.userType === globals.USER_TYPES.PLAYER
|| p.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
p => (p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR)
|| p.killed
),
this.killPlayerHandlers,
@@ -168,7 +167,9 @@ export class InProgress {
if (killedPerson) {
killedPerson.out = true;
killedPerson.killed = true;
killedPerson.userType = globals.USER_TYPES.KILLED_PLAYER;
killedPerson.userType = killedPerson.userType === globals.USER_TYPES.BOT
? globals.USER_TYPES.KILLED_BOT
: globals.USER_TYPES.KILLED_PLAYER;
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(killedPerson.name + ' killed.', 'success', true, true, 'medium');
this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === globals.STATUS.ENDED);
@@ -253,14 +254,12 @@ export class InProgress {
});
const teamGood = this.stateBucket.currentGameState.people.filter(
(p) => p.alignment === globals.ALIGNMENT.GOOD
&& (p.userType === globals.USER_TYPES.PLAYER
|| p.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
&& ((p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR)
|| p.killed)
);
const teamEvil = this.stateBucket.currentGameState.people.filter((p) => p.alignment === globals.ALIGNMENT.EVIL
&& (p.userType === globals.USER_TYPES.PLAYER
|| p.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
&& ((p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR)
|| p.killed)
);
this.renderGroupOfPlayers(
@@ -385,7 +384,8 @@ export class InProgress {
);
if (document.querySelectorAll('.potential-moderator').length === 0) {
document.getElementById('transfer-mod-modal-content').innerText = 'There is nobody available to transfer to.';
document.getElementById('transfer-mod-modal-content').innerText =
'There is nobody available to transfer to. Only spectators or killed players (who are not bots) can be mods.';
}
}

View File

@@ -82,7 +82,7 @@ export class Lobby {
const lobbyPlayersContainer = this.container.querySelector('#lobby-players');
const sorted = this.stateBucket.currentGameState.people.sort(
function (a, b) {
if (a.userType === globals.USER_TYPES.MODERATOR) {
if (a.userType === globals.USER_TYPES.MODERATOR || a.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
return -1;
}
return 1;
@@ -92,7 +92,7 @@ export class Lobby {
lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name, person.userType));
}
const playerCount = this.stateBucket.currentGameState.people.filter(
p => p.userType === globals.USER_TYPES.PLAYER || p.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
p => p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR
).length;
document.querySelector("label[for='lobby-players']").innerText =
'Participants (' + playerCount + '/' + this.stateBucket.currentGameState.gameSize + ' Players)';
@@ -194,7 +194,7 @@ function renderLobbyPerson (name, userType) {
personNameEl.innerText = name;
personTypeEl.innerText = userType + globals.USER_TYPE_ICONS[userType];
el.classList.add('lobby-player');
if (userType === globals.USER_TYPES.MODERATOR) {
if (userType === globals.USER_TYPES.MODERATOR || userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
el.classList.add('moderator');
}

View File

@@ -209,6 +209,12 @@ a {
text-decoration: none;
}
.checkbox {
border-radius: 5px;
border: 2px solid #d7d7d7;
padding: 10px;
}
textarea, input {
font-family: 'signika-negative', sans-serif;
font-size: 16px;
@@ -572,7 +578,8 @@ input {
}
.good, .compact-card.good .card-role {
color: #5f7cfb;
color: #5f7cfb !important;
font-weight: bold;
}
.good-players, #deck-good {
@@ -584,7 +591,8 @@ input {
}
.evil, .compact-card.evil .card-role {
color: #e73333;
color: #dd2929 !important;
font-weight: bold;
}
@keyframes placeholder {

View File

@@ -7,7 +7,7 @@
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #2d2c38;
background-color: #16141e;
align-items: center;
justify-content: center;
max-width: 25em;

View File

@@ -314,6 +314,15 @@ option {
margin: 0 auto;
}
#step-4 div:nth-child(2) {
margin-top: 25px;
}
#step-4 label[for="test-game"] {
display: block;
margin-bottom: 10px;
}
#step-4 input {
padding: 15px 5px;
width: 95%;

View File

@@ -3,7 +3,7 @@
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
background-color: #171522;
background-color: #000000;
color: #e7e7e7;
padding: 5px;
border-radius: 3px;
@@ -19,6 +19,12 @@
.potential-moderator {
margin: 0.5em auto;
border: 2px solid #46455299;
background: #4645523b;
}
#transfer-mod-modal h2 {
margin-bottom: 15px;
}
#lobby-players {
@@ -615,14 +621,14 @@ label[for='moderator'] {
canvas {
border-radius: 5px;
margin: 1em;
margin: 20px 5px;
}
.game-player {
border-left: 3px solid #21ba45;
display: flex;
color: #d7d7d7;
background-color: #171522;
color: #f1f1f1;
background-color: #000000;
align-items: center;
padding: 0 5px;
justify-content: space-between;
@@ -714,6 +720,7 @@ canvas {
.game-player-role {
min-width: 7em;
color: #bbbbbb;
}
.game-player.killed {
@@ -800,9 +807,10 @@ canvas {
padding: 10px;
border-radius: 5px;
min-height: 25em;
background-color: #3b3a4a;
border: 1px solid #46455299;
background: #4645523b;
max-width: 35em;
min-width: 17em;
min-width: 19em;
margin-top: 1em;
}

View File

@@ -100,7 +100,9 @@ const globals = {
PLAYER: 'player',
TEMPORARY_MODERATOR: 'temp mod',
KILLED_PLAYER: 'killed',
SPECTATOR: 'spectator'
KILLED_BOT: 'killed bot',
SPECTATOR: 'spectator',
BOT: 'bot'
},
ERROR_MESSAGE: {
GAME_IS_FULL: 'This game is full',

View File

@@ -9,7 +9,8 @@ class Game {
hasDedicatedModerator,
originalModeratorId,
createTime,
timerParams = null
timerParams = null,
isTestGame = false
) {
this.accessCode = accessCode;
this.status = status;
@@ -26,7 +27,7 @@ class Game {
this.previousModeratorId = null;
this.createTime = createTime;
this.timerParams = timerParams;
this.isFull = this.gameSize === 1 && !this.hasDedicatedModerator;
this.isFull = (this.gameSize === 1 && !this.hasDedicatedModerator) || isTestGame;
this.timeRemaining = null;
}
}

View File

@@ -6,17 +6,19 @@ class GameCreationRequest {
hasTimer,
timerParams,
moderatorName,
hasDedicatedModerator
hasDedicatedModerator,
isTestGame
) {
this.deck = deck;
this.hasTimer = hasTimer;
this.timerParams = timerParams;
this.moderatorName = moderatorName;
this.hasDedicatedModerator = hasDedicatedModerator;
this.isTestGame = isTestGame;
}
static validate = (gameParams) => {
const expectedKeys = ['deck', 'hasTimer', 'timerParams', 'moderatorName', 'hasDedicatedModerator'];
const expectedKeys = ['deck', 'hasTimer', 'timerParams', 'moderatorName', 'hasDedicatedModerator', 'isTestGame'];
if (gameParams === null
|| typeof gameParams !== 'object'
|| expectedKeys.some((key) => !Object.keys(gameParams).includes(key))
@@ -31,6 +33,7 @@ class GameCreationRequest {
function valid (gameParams) {
return typeof gameParams.hasTimer === 'boolean'
&& typeof gameParams.isTestGame === 'boolean'
&& typeof gameParams.hasDedicatedModerator === 'boolean'
&& typeof gameParams.moderatorName === 'string'
&& gameParams.moderatorName.length > 0

View File

@@ -86,7 +86,9 @@ const Events = [
stateChange: async (game, socketArgs, vars) => {
const person = game.people.find((person) => person.id === socketArgs.personId);
if (person && !person.out) {
person.userType = globals.USER_TYPES.KILLED_PLAYER;
person.userType = person.userType === globals.USER_TYPES.BOT
? globals.USER_TYPES.KILLED_BOT
: globals.USER_TYPES.KILLED_PLAYER;
person.out = true;
person.killed = true;
}

View File

@@ -59,8 +59,10 @@ class GameManager {
gameParams.hasTimer,
gameParams.timerParams,
gameParams.moderatorName,
gameParams.hasDedicatedModerator
gameParams.hasDedicatedModerator,
gameParams.isTestGame
);
console.log(req.isTestGame);
const newAccessCode = await this.generateAccessCode(globals.ACCESS_CODE_CHAR_POOL);
if (newAccessCode === null) {
return Promise.reject(globals.ERROR_MESSAGE.NO_UNIQUE_ACCESS_CODE);
@@ -76,14 +78,15 @@ class GameManager {
const newGame = new Game(
newAccessCode,
globals.STATUS.LOBBY,
initializePeopleForGame(req.deck, moderator, this.shuffle),
initializePeopleForGame(req.deck, moderator, this.shuffle, req.isTestGame),
req.deck,
req.hasTimer,
moderator.id,
req.hasDedicatedModerator,
moderator.id,
new Date().toJSON(),
req.timerParams
req.timerParams,
req.isTestGame
);
await this.eventManager.publisher.set(newAccessCode, JSON.stringify(newGame), {
EX: globals.STALE_GAME_SECONDS
@@ -242,6 +245,10 @@ class GameManager {
game.people[i].userType = globals.USER_TYPES.PLAYER;
game.people[i].out = false;
}
if (game.people[i].userType === globals.USER_TYPES.KILLED_BOT) {
game.people[i].userType = globals.USER_TYPES.BOT;
game.people[i].out = false;
}
game.people[i].revealed = false;
game.people[i].killed = false;
if (game.people[i].gameRole) {
@@ -314,7 +321,7 @@ function initializeModerator (name, hasDedicatedModerator) {
return new Person(createRandomId(), createRandomId(), name, userType);
}
function initializePeopleForGame (uniqueRoles, moderator, shuffle) {
function initializePeopleForGame (uniqueRoles, moderator, shuffle, isTestGame) {
const people = [];
const cards = [];
@@ -335,10 +342,11 @@ function initializePeopleForGame (uniqueRoles, moderator, shuffle) {
createRandomId(),
createRandomId(),
UsernameGenerator.generate(),
globals.USER_TYPES.PLAYER,
isTestGame ? globals.USER_TYPES.BOT : globals.USER_TYPES.PLAYER,
cards[j].role,
cards[j].description,
cards[j].team
cards[j].team,
isTestGame
);
person.customRole = cards[j].custom;
person.hasEnteredName = false;