Finished player choosing entry implementation; TODO: Award in non Buzzer Interaction

This commit is contained in:
EisiBaer 2023-07-13 20:22:46 +02:00
parent a798ec1558
commit 5ef8ea86bb
12 changed files with 200 additions and 74 deletions

View File

@ -63,26 +63,6 @@ exports.setAllPlayersAcceptAnswers = ( playerIds, acceptAnswersValue ) => {
});
}
/**
* Sets one player as isChoosing and other players as isChoosing false
* @param {String} choosingPlayerId
* @param {Array} notChoosingPlayerIds
* @returns A promise which resolves with the choosing player
*/
exports.setAllPlayersAcceptAnswers = ( choosingPlayerId, notChoosingPlayerIds ) => {
return new Promise((resolve, reject) => {
let promiseSetChoosing = PlayerModel.updateOne( { _id: choosingPlayerId }, { isChoosing: true } );
let promiseSetNotChoosing = PlayerModel.updateMany( { _id: { $in : notChoosingPlayerIds } }, { isChoosing: false } );
Promise.all( [ promiseSetChoosing, promiseSetNotChoosing ] )
.then( ( resArr ) => {
resolve( resArr[0] );
})
.catch( ( err ) => {
reject( err );
})
});
}
/**
* Checks if a player is allowed to currently answer a question
* @param {String} playerId

View File

@ -8,6 +8,7 @@ const GameSchema = new Schema({
board: { type: Schema.Types.ObjectId, ref: "Board", required: false },
players: { type: [Schema.Types.ObjectId], ref: "Player", required: true },
acceptAnswers: { type: Boolean, required: true, default: false },
playerChoosing: { type: Object },
answeredBoardEntries: { type: [Object], default: [] },
state: { type: String, required: true, default: "CREATED" },
createdTimestamp: { type: Date, required: true, default: new Date() },

View File

@ -128,22 +128,27 @@ exports.handleMessage = ( gameSocketList, socket, dataRaw ) => {
break;
case "startGame":
if( socket.locals.isHost ){
let gameOuter;
gameController.findGameByIdAndSetStateAndPopulateBoard( socket.locals.game, "IN_PROGRESS" )
.then( ( game ) => {
gameOuter = game;
if( game.players.length > 0 ){
let randomPlayerIndex = crypto.randomInt(0, game.players.length );
let randomPlayer = game.players[randomPlayerIndex];
let otherPlayers = game.players.splice(randomPlayerIndex, 1);
return playerController.setPlayerIsChoosingAndOthersNotChoosing( randomPlayer, otherPlayers );
let randomPlayer = game.players[ crypto.randomInt(0, game.players.length ) ];
game.playerChoosing = {
playerId: randomPlayer._id.toString(),
categoryIndex: null,
boardEntryIndex: null,
}
return game.save();
} else {
return -1;
return game;
}
})
.then( ( choosingPlayer ) => {
.then( ( game ) => {
let choosingPlayer = undefined
if( game.playerChoosing ){
choosingPlayer = game.playerChoosing.playerId;
}
let message = "Game is starting";
let sendingData = { gameId: gameOuter._id, board: gameOuter.board, choosingPlayer: choosingPlayer._id };
let sendingData = { gameId: game._id, board: game.board, choosingPlayer: choosingPlayer };
sendAllPlayers( socket, gameSocketList, "gameStarted", message, sendingData);
resolve();
})
@ -164,19 +169,69 @@ exports.handleMessage = ( gameSocketList, socket, dataRaw ) => {
reject( new Error("Message not sent by host") );
}
break;
case "letPlayerChoose":
if( socket.locals.isHost ){
gameController.findGameById( socket.locals.game )
.then( ( game ) => {
if( data.payload.playerId ){
game.playerChoosing = {
playerId: data.payload.playerId,
categoryIndex: null,
boardEntryIndex: null,
}
return game.save();
} else {
let currentChoosingPlayer = game.players.findIndex( playerEntry => playerEntry._id.toString() === game.playerChoosing.playerId );
if( currentChoosingPlayer !== -1 ){
let newChoosingPlayerIndex = currentChoosingPlayer + 1;
if( newChoosingPlayerIndex === game.players.length ){
newChoosingPlayerIndex = 0;
}
game.playerChoosing = {
playerId: game.players[newChoosingPlayerIndex]._id.toString(),
categoryIndex: null,
boardEntryIndex: null,
}
return game.save();
} else {
return game;
}
}
})
.then( ( game ) => {
let message = `Player ${game.playerChoosing.playerId} can choose a BoardEntry`;
let sendingData = { choosingPlayer: game.playerChoosing.playerId };
sendAllPlayers( socket, gameSocketList, "playerCanChoose", message, sendingData );
resolve();
})
.catch( ( err ) => {
reject( err );
});
}
break;
case "playerChooseBoardEntry":
playerController.checkPlayerCanChoose( socket.locals.player )
.then( ( player ) => {
if( player.isChoosing ){
let message = `Category ${payload.categoryIndex} with BoardEntry ${payload.boardEntryIndex} selected`;
let sendingData = { categoryIndex: payload.categoryIndex, boardEntryIndex: payload.boardEntryIndex, choosingPlayer: player._id.toString() };
sendToHost( socket, gameSocketList, "playerChoseBoardEntry", message, sendingData );
resolve();
gameController.findGameById( socket.locals.game )
.then( ( game ) => {
if( socket.locals.player === game.playerChoosing.playerId ){
game.playerChoosing.categoryIndex = payload.categoryIndex;
game.playerChoosing.boardEntryIndex = payload.boardEntryIndex;
return game.save();
} else {
resolve();
throw new Error("Message not sent by choosing Player");
}
})
.catch();
.then( ( game ) => {
let chosenCategoryIndex = game.playerChoosing.categoryIndex;
let chosenBoardEntryIndex = game.playerChoosing.boardEntryIndex;
let choosingPlayer = game.playerChoosing.playerId;
let message = `Category ${chosenCategoryIndex} with BoardEntry ${chosenBoardEntryIndex} selected`;
let sendingData = { categoryIndex: chosenCategoryIndex, boardEntryIndex: chosenBoardEntryIndex, choosingPlayer: choosingPlayer };
sendAllPlayers( socket, gameSocketList, "playerChoseBoardEntry", message, sendingData );
resolve();
})
.catch( ( err ) => {
reject( err );
});
break;
case "selectBoard":
if( socket.locals.isHost ){

View File

@ -4,6 +4,14 @@ import { computed } from 'vue';
const props = defineProps({
boardEntry: Object,
boardEntryIndex: {
type: Number,
default: -1,
},
categoryIndex: {
type: Number,
default: -1,
},
isHost: {
type: Boolean,
default: false,
@ -16,10 +24,7 @@ const props = defineProps({
type: Boolean,
default: false,
},
playerChose: {
type: [Boolean, String],
default: false,
},
chosenEntry: Object,
});
const emit = defineEmits(["boardEntryCardClicked", "boardEntryAnsweredClicked", "boardEntryAnsweredRevertClicked" ]);
@ -64,9 +69,9 @@ function boardEntryAnsweredRevertClicked(){
<font-awesome-icon icon="fa-solid fa-square-minus" size="lg" />
</button>
</div>
<div v-if="playerChose !== false" class="position-absolute start-0 bottom-0 mb-2 w-100">
<span class="bg-pink-accent-primary p-1 ms-2 max-w-50" @click.stop="boardEntryAnsweredRevertClicked">
{{ props.playerChose.name }} chooses this
<div v-if="props.chosenEntry !== undefined && ( props.chosenEntry.categoryIndex === props.categoryIndex && props.chosenEntry.boardEntryIndex === props.boardEntryIndex )" class="position-absolute start-0 bottom-0 mb-2 w-100">
<span class="bg-pink-accent-primary p-1 ms-2 max-w-50 rounded-1" @click.stop="boardEntryAnsweredRevertClicked">
{{ props.chosenEntry.player.name }}
</span>
</div>
<h5 class="mb-0 user-select-none">

View File

@ -9,29 +9,47 @@ const props = defineProps({
acceptAnswers: {
type: Boolean,
default: false,
}
},
isEntryShown: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["manualPointsAdjustment", "answerRuled", "revealPlayerAnswer"]);
const emit = defineEmits(["manualPointsAdjustment", "answerRuled", "revealPlayerAnswer", "letPlayerChoose"]);
function revealAnswer(){
emit("revealPlayerAnswer", props.player._id);
}
function letPlayerChoose(){
emit("letPlayerChoose", props.player._id);
}
</script>
<template>
<div class="card bg-primary my-1 player-card">
<div class="card-body p-2">
<h5 class="mb-0 text-truncate">
<template v-if="props.player.isAnswering">
<font-awesome-layers :title="'Player is answering'">
<font-awesome-icon class="text-pink-accent-primary" icon="fa-solid fa-square" size="lg" fade style="--fa-animation-duration:2s" />
<font-awesome-icon class="text-white" icon="fa-solid fa-angle-right" size="sm" />
</font-awesome-layers>
</template>
{{ props.player.name }}
</h5>
<div class="row">
<div class="col position-relative">
<h5 class="mb-0 text-truncate">
<template v-if="props.player.isAnswering">
<font-awesome-layers :title="'Player is answering'">
<font-awesome-icon class="text-pink-accent-primary" icon="fa-solid fa-square" size="lg" fade style="--fa-animation-duration:2s" />
<font-awesome-icon class="text-white" icon="fa-solid fa-angle-right" size="sm" />
</font-awesome-layers>
</template>
{{ props.player.name }}
</h5>
<span
v-show="props.player.isChoosing === true"
class="position-absolute end-0 top-50 translate-middle-y bg-pink-accent-primary rounded-1 me-2 px-1"
>
<font-awesome-icon class="text-light" icon="fa-solid fa-hand-pointer" />
</span>
</div>
</div>
<div v-if="props.isHost || props.player.currentTextAnswer" class="row mt-2">
<div class="col-sm-2 text-nowrap">
@ -41,12 +59,15 @@ function revealAnswer(){
<input type="text" class="form-control form-control-sm border-0" :value="props.player.currentTextAnswer" readonly>
</div>
</div>
<div v-if="props.isHost && !props.acceptAnswers" class="row mt-2">
<div v-if="props.isHost" class="row mt-2">
<div class="col-sm-2 text-nowrap d-flex justify-content-start align-items-center">
Reveal
Actions
</div>
<div class="col-sm-10">
<button class="btn btn-sm btn-pink-accent-primary" @click="revealAnswer">Reveal Answer</button>
<div class="col-sm-5 text-center">
<button class="btn btn-sm btn-pink-accent-primary w-100 h-100" @click="revealAnswer" :disabled="!props.isEntryShown || props.acceptAnswers">Reveal Answer</button>
</div>
<div class="col-sm-5 text-center">
<button class="btn btn-sm btn-pink-accent-primary w-100 h-100" @click="letPlayerChoose" :disabled="props.isEntryShown">Let Choose</button>
</div>
</div>
<div class="row mt-2">

View File

@ -64,6 +64,10 @@ const anyPlayerIsAnswering = computed( () => {
return gameStore.players.findIndex( playerEntry => playerEntry.isAnswering ) !== -1;
});
const isBeingPlayed = computed( () => {
return route.path.includes("game");
});
function boardSelected(){
selectedObject.value = gameStore.board;
@ -78,6 +82,7 @@ function selectBoardEntryWithCategory( cIndex, entryIndex ){
function setUpListeners(){
gameStore.addSocketListener("boardEntrySelected", ( data ) => {
gameStore.acceptAnswers = false;
showingAnswer.value = false;
showingQuestion.value = false;
selectBoardEntryWithCategory( data.payload.categoryIndex, data.payload.boardEntryIndex );
@ -151,6 +156,9 @@ function setUpListeners(){
});
gameStore.addSocketListener("questionLocked", ( _data ) => {
gameStore.acceptAnswers = false;
for( let i in gameStore.players ){
gameStore.players[i].isAnswering = true;
}
});
gameStore.addSocketListener("playerAnswersRevealed", ( data ) => {
for( let playerAnswer of data.payload.revealedAnswers ){
@ -195,6 +203,9 @@ function setUpListeners(){
}
}
});
gameStore.addSocketListener("playerCanChoose", ( data ) => {
gameStore.setPlayerOnIndexChoosing( data.payload.choosingPlayer );
});
}
function playerBuzzered( data ){
@ -216,7 +227,7 @@ function removeListeners(){
gameStore.removeSocketListener("boardEntrySelected");
gameStore.removeSocketListener("boardSelected");
gameStore.removeSocketListener("pointsAdjusted");
gameStore.removeSocketListener("playerAnswerRevealed");boardIsLoading
gameStore.removeSocketListener("playerAnswerRevealed");
gameStore.removeSocketListener("playerAnswerTextUpdated");
gameStore.removeSocketListener("audioPlaying")
gameStore.removeSocketListener("audioStopped")
@ -325,6 +336,18 @@ function lockQuestion(){
gameStore.sendEvent("lockQuestion", {});
}
function letNextPlayerChoose(){
if( gameStore.isHost ){
gameStore.sendEvent("letPlayerChoose", {});
}
}
function letSpecificPlayerChoose( playerId ){
if( gameStore.isHost ){
gameStore.sendEvent("letPlayerChoose", { playerId: playerId } );
}
}
function revealPlayerAnswers( playerIds ){
let payload = [];
let ids;
@ -430,10 +453,12 @@ onBeforeRouteLeave((to, from) => {
:questionPoints="(isBoardSelected ? 0 : Number(selectedObject.points) )"
:answerInteraction="(isBoardSelected ? '' : selectedObject.answer.answerInteraction )"
:isHost="gameStore.isHost"
:acceptAnswers="gameStore.acceptAnswers && !isBoardSelected"
:acceptAnswers="gameStore.acceptAnswers"
:isEntryShown="!isBoardSelected"
@manualPointsAdjustment="manualPointsAdjustment"
@answerRuled="answerRuled"
@revealPlayerAnswers="revealPlayerAnswers"
@letPlayerChoose="letSpecificPlayerChoose"
/>
</div>
@ -460,6 +485,7 @@ onBeforeRouteLeave((to, from) => {
@revealPlayerAnswers="revealPlayerAnswers"
@questionAnswered="questionAnswered"
@questionAnsweredRevert="questionAnsweredRevert"
@letNextPlayerChoose="letNextPlayerChoose"
/>
</div>

View File

@ -32,6 +32,10 @@ onMounted( () => {
if( !gameStore.isHost ){
gameStore.isPlayerChoosing = data.payload.choosingPlayer === gameStore.playerId;
}
let choosingPlayerIndex = gameStore.players.findIndex( playerEntry => playerEntry._id === data.payload.choosingPlayer );
if( choosingPlayerIndex !== -1 ){
gameStore.players[choosingPlayerIndex].isChoosing = true;
}
router.push( { name: "gameWithGameId", params: { gameId: route.params.gameId } } );
});

View File

@ -21,13 +21,13 @@ const props = defineProps({
const emit = defineEmits(["boardEntryClicked", "questionAnswered", "questionAnsweredRevert" ])
function playerChosenBoardEntry( categoryIndex, boardEntryIndex ){
if( props.chosenEntry !== undefined && ( props.chosenEntry.categoryIndex === categoryIndex || props.chosenEntry.boardEntryIndex === boardEntryIndex ) ){
return props.chosenEntry.player.name;
} else {
return false;
}
}
// function playerChosenBoardEntry( categoryIndex, boardEntryIndex ){
// if( props.chosenEntry !== undefined && ( props.chosenEntry.categoryIndex === categoryIndex || props.chosenEntry.boardEntryIndex === boardEntryIndex ) ){
// return props.chosenEntry.player.name;
// } else {
// return false;
// }
// }
function boardEntryCardClicked( categoryIndex, boardEntryIndex ){
if( props.isHost || props.isPlayerChoosing ){
@ -58,7 +58,9 @@ function boardEntryCardClicked( categoryIndex, boardEntryIndex ){
:isPlayerChoosing="props.isPlayerChoosing"
:isBeingPlayed="props.isBeingPlayed"
:boardEntry="boardEntry"
:playerChose="playerChosenBoardEntry( categoryIndex, boardEntryIndex )"
:boardEntryIndex="boardEntryIndex"
:categoryIndex="categoryIndex"
:chosenEntry="props.chosenEntry"
@boardEntryCardClicked="boardEntryCardClicked( categoryIndex, boardEntryIndex )"
@boardEntryAnsweredClicked="emit( 'questionAnswered', categoryIndex, boardEntryIndex )"
@boardEntryAnsweredRevertClicked="emit( 'questionAnsweredRevert', categoryIndex, boardEntryIndex )"

View File

@ -12,7 +12,7 @@ const props = defineProps({
}
});
const emit = defineEmits( "lockQuestion", "revealPlayerAnswers" );
const emit = defineEmits( "lockQuestion", "revealPlayerAnswers", "letNextPlayerChoose" );
let boardEntry = computed( () => {
if( props.objToDisplay === "BoardEntry" ){
@ -29,6 +29,10 @@ function revealPlayerAnswers(){
emit("revealPlayerAnswers");
}
function letNextPlayerChoose(){
emit("letNextPlayerChoose");
}
</script>
@ -51,6 +55,16 @@ function revealPlayerAnswers(){
</div>
</div>
</div>
<div
v-else
class="d-flex flex-column justify-content-center border-top border-pink-accent-primary interaction-size"
>
<div class="row mx-2">
<div class="col-12 text-center">
<button class="btn btn-pink-accent-primary" @click="letNextPlayerChoose">Let Next Player Choose</button>
</div>
</div>
</div>
</template>
<style scoped>

View File

@ -17,10 +17,14 @@ const props = defineProps({
acceptAnswers: {
type: Boolean,
default: false,
}
},
isEntryShown: {
type: Boolean,
default: false,
},
})
const emit = defineEmits( "manualPointsAdjustment", "answerRuled", "revealPlayerAnswers" );
const emit = defineEmits( "manualPointsAdjustment", "answerRuled", "revealPlayerAnswers", "letPlayerChoose" );
let buttonDivHeight = ref("3rem");
let navbarHeight = ref("4rem");
@ -46,6 +50,10 @@ function revealPlayerAnswer( playerId ){
emit("revealPlayerAnswers", [playerId] );
}
function letPlayerChoose( playerId ){
emit("letPlayerChoose", playerId );
}
</script>
<template>
@ -64,9 +72,11 @@ function revealPlayerAnswer( playerId ){
:player="player"
:isHost="props.isHost"
:acceptAnswers="props.acceptAnswers"
:isEntryShown="props.isEntryShown"
@manualPointsAdjustment="( arePointsAdded ) => manualPointsAdjustment( player._id, player.name, arePointsAdded )"
@answerRuled="( isAnswerCorrect ) => answerRuled( player._id, player.name, isAnswerCorrect )"
@revealPlayerAnswer="revealPlayerAnswer"
@letPlayerChoose="letPlayerChoose"
/>
</template>
</div>

View File

@ -4,7 +4,7 @@ import { createPinia } from "pinia";
import { FontAwesomeIcon, FontAwesomeLayers } from "@fortawesome/vue-fontawesome";
import { library } from "@fortawesome/fontawesome-svg-core";
import { faDragon, faRightToBracket, faUsers, faUserPlus, faSpinner, faPlusSquare, faBorderAll, faPen, faTrash, faAngleDown, faAngleUp,
faPlus, faMinus, faAngleRight, faSquare, faPlay, faCircleExclamation, faSquareCheck, faSquareMinus } from "@fortawesome/free-solid-svg-icons";
faPlus, faMinus, faAngleRight, faSquare, faPlay, faCircleExclamation, faSquareCheck, faSquareMinus, faHandPointer } from "@fortawesome/free-solid-svg-icons";
import { faCircleUser, faSquarePlus } from "@fortawesome/free-regular-svg-icons";
@ -39,6 +39,7 @@ library.add({
faSquareMinus,
faPlay,
faCircleExclamation,
faHandPointer,
});

View File

@ -28,6 +28,13 @@ export const useGameStore = defineStore('game', {
}
},
actions: {
setPlayerOnIndexChoosing( playerId ){
let playerIndex = this.players.findIndex( playerEntry => playerEntry._id === playerId );
for( let i in this.players ){
this.players[i].isChoosing = Number( i ) === Number( playerIndex );
}
this.isPlayerChoosing = playerId === this.playerId;
},
setBoardWithConversion( board ){
this.board = boardResponseToBoardModel( board );
},