save and store selection

This commit is contained in:
Settel 2021-08-30 18:21:14 +02:00
parent 0e5084144f
commit d79e3a8956
18 changed files with 180 additions and 51 deletions

View File

@ -2,6 +2,7 @@
<nav v-if="isGamemaster" class="gamecontrols"> <nav v-if="isGamemaster" class="gamecontrols">
<button @click="startGame">Start</button> <button @click="startGame">Start</button>
<button @click="resetGame">Reset</button> <button @click="resetGame">Reset</button>
<button @click="continueGame">Continue</button>
</nav> </nav>
</template> </template>
@ -23,6 +24,9 @@ export default {
resetGame() { resetGame() {
this.$engine.resetGame() this.$engine.resetGame()
}, },
continueGame() {
this.$engine.continueGame()
},
}, },
} }
</script> </script>

View File

@ -3,8 +3,8 @@
<div class="play__layout"> <div class="play__layout">
<div class="play__layout-playground"> <div class="play__layout-playground">
<Quote :text="quote" /> <Quote :text="quote" />
<Sources :sources="sources" /> <Sources :sources="sources" :selectable="selectable" />
<ConfirmButton /> <ConfirmButton v-if="selectable"/>
</div> </div>
<div class="play__layout-right-column"> <div class="play__layout-right-column">
<PlayerList :players="players" /> <PlayerList :players="players" />
@ -28,6 +28,14 @@ export default {
players() { players() {
return this.$store.state.players.players return this.$store.state.players.players
}, },
selectable() {
const userId = this.$store.state.engine.user.id
const selection = this.$store.state.round.selections[userId]
if (this.$store.state.game.phase != 'select-quote') return false
if (typeof selection === 'undefined') return true
return !selection
},
}, },
} }
</script> </script>

View File

@ -29,7 +29,6 @@ export default {
&__player { &__player {
display: flex; display: flex;
// height: 1.4em;
color: #ffffff; color: #ffffff;
font-family: Dosis; font-family: Dosis;
font-size: 18px; font-size: 18px;

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="source"> <div class="source">
<div <div
:class="['source__container', { 'source__container__selected': selected } ]" :class="getCssClass"
@click="clicked" @click="clicked"
> >
<div class="source__source"> <div class="source__source">
@ -13,10 +13,14 @@
<script> <script>
export default { export default {
props: ['source'], props: ['source', 'selectable'],
computed: { computed: {
selected() { getCssClass() {
return this.isSelected() return {
'source__container': true,
'source__container-selectable': this.selectable,
'source__container__selected': this.isSelected(),
}
}, },
}, },
methods: { methods: {
@ -24,6 +28,8 @@ export default {
return this.$store.state.selection.selection[this.source.id] return this.$store.state.selection.selection[this.source.id]
}, },
clicked() { clicked() {
if (!this.selectable) return
if (this.isSelected()) { if (this.isSelected()) {
this.$store.commit('selection/unselect', this.source.id) this.$store.commit('selection/unselect', this.source.id)
} else { } else {
@ -53,16 +59,21 @@ export default {
background-color: #402080; background-color: #402080;
color: #ffffff; color: #ffffff;
&:hover { &-selectable {
background-color: #8060c0; &:hover {
cursor: pointer; background-color: #8060c0;
cursor: pointer;
}
&.source__container__selected {
background-color: #ffffff;
box-shadow: 0 0 10px #ffffff;
}
} }
&__selected, &__selected {
&__selected:hover { background-color: #d0d0d0;
background-color: #ffffff;
color: #402080; color: #402080;
box-shadow: 0 0 10px #ffffff; box-shadow: 0 0 10px #d0d0d0;
} }
} }

View File

@ -6,6 +6,7 @@
v-for="(source, idx) in sources" v-for="(source, idx) in sources"
:class="['sources__source-' + idx]" :class="['sources__source-' + idx]"
:source="source" :source="source"
:selectable="selectable"
:key="source.id" :key="source.id"
/> />
</div> </div>
@ -16,7 +17,7 @@
<script> <script>
export default { export default {
props: ['sources'], props: ['sources', 'selectable'],
mounted() { mounted() {
this.$store.commit('selection/clearSelection') this.$store.commit('selection/clearSelection')
}, },

View File

@ -0,0 +1,7 @@
export default async function() {
const { store } = this.context
await this.callApi('/api/continueGame', {
g: store.state.engine.user?.game,
})
}

View File

@ -5,6 +5,7 @@ import fetchUpdate from './fetchUpdate'
import fetchUserInfo from './fetchUserInfo' import fetchUserInfo from './fetchUserInfo'
import startGame from './startGame' import startGame from './startGame'
import resetGame from './resetGame' import resetGame from './resetGame'
import continueGame from './continueGame'
import parseSyncData from './parseSyncData' import parseSyncData from './parseSyncData'
import saveSelection from './saveSelection' import saveSelection from './saveSelection'
@ -22,6 +23,7 @@ export default (context, inject) => {
fetchUserInfo, fetchUserInfo,
startGame, startGame,
resetGame, resetGame,
continueGame,
parseSyncData, parseSyncData,
saveSelection, saveSelection,
} }

View File

@ -1,8 +1,12 @@
export default async function(selection) { export default async function(selection) {
const { store } = this.context const { store } = this.context
if (selection.length === 0) {
selection = ['-']
}
await this.callApi('/api/saveSelection', { await this.callApi('/api/saveSelection', {
g: store.state.engine.user?.game, g: store.state.engine.user?.game,
selection: selection.join(','), selection: selection[0],
}) })
} }

View File

@ -1,6 +1,7 @@
export const state = () => ({ export const state = () => ({
quote: "", quote: "",
sources: [], sources: [],
selections: {},
}) })
export const mutations = { export const mutations = {
@ -8,9 +9,11 @@ export const mutations = {
if (game && game.round) { if (game && game.round) {
state.quote = game.round.quote state.quote = game.round.quote
state.sources = game.round.sources state.sources = game.round.sources
state.selections = game.round.selections
} else { } else {
state.quote = "" state.quote = ""
state.sources = [] state.sources = []
state.selections = {}
} }
}, },
} }

View File

@ -0,0 +1,25 @@
package application
import (
"fmt"
"net/http"
"sirlab.de/go/knyt/user"
)
func (app *Application) ContinueGame(usr *user.User, w http.ResponseWriter, r *http.Request) {
gameRef := r.URL.Query().Get("g")
gm, err := app.GetGameById(gameRef)
if err != nil {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "game not found")
return
}
if usr.GetGameId() != gameRef || !usr.IsGamemaster() {
w.WriteHeader(http.StatusForbidden)
fmt.Fprintf(w, "forbidden")
return
}
gm.ContinueGame()
}

View File

@ -0,0 +1,53 @@
package game
import (
"fmt"
)
func (gm *Game) ContinueGame() {
state, phase := gm.GetState()
if state != STATE_PLAY {
fmt.Printf("invalid state, can't continue game in %s state\n", state)
return
}
switch phase {
case PHASE_SELECT_QUOTE:
gm.proceedToReveal()
default:
fmt.Printf("invalid state, can't continue game in %s state, %s phase\n", state, phase)
return
}
}
func (gm *Game) proceedToReveal() {
if !gm.allPlayersHaveSelectedAQuote() {
fmt.Println("not all players have selected a quote yet")
return
}
err := gm.changeGamePhase(STATE_PLAY, PHASE_SELECT_QUOTE, PHASE_REVEAL_START)
if err != nil {
fmt.Println(err)
return
}
gm.notifyClients()
}
func (gm *Game) allPlayersHaveSelectedAQuote() bool {
gm.mu.Lock()
defer gm.mu.Unlock()
for id, playerInfo := range gm.players {
if !playerInfo.isPlaying || playerInfo.isIdle {
continue
}
if len(gm.round.selections[id]) == 0 {
return false
}
}
return true
}

View File

@ -40,11 +40,11 @@ func (gm *Game) GetId() string {
return gm.id return gm.id
} }
func (gm *Game) GetState() string { func (gm *Game) GetState() (string, string) {
gm.mu.Lock() gm.mu.Lock()
defer gm.mu.Unlock() defer gm.mu.Unlock()
return gm.state return gm.state, gm.phase
} }
func (gm *Game) GetName() string { func (gm *Game) GetName() string {

View File

@ -61,31 +61,28 @@ func (gm *Game) getRoundInfo() *syncdata.RoundInfo {
}) })
} }
playerState := make([]syncdata.PlayerState, 0) roundInfo := syncdata.RoundInfo{
Quote: quote.GetQuote(),
Sources: sources,
Selections: gm.getSelections(),
}
return &roundInfo
}
func (gm *Game) getSelections() map[string]bool {
selections := make(map[string]bool, 0)
for id, playerInfo := range gm.players { for id, playerInfo := range gm.players {
if !playerInfo.isPlaying { if !playerInfo.isPlaying {
continue continue
} }
state := syncdata.PLAYER_STATE_UNDECIDED if len(gm.round.selections[id]) > 0 {
selection := gm.round.selection[id] selections[id] = true
if len(selection) > 0 { } else if !playerInfo.isIdle {
state = syncdata.PLAYER_STATE_DECIDED selections[id] = false
} else if playerInfo.isIdle {
state = syncdata.PLAYER_STATE_IDLE
} }
playerState = append(playerState, syncdata.PlayerState{
Id: id,
State: state,
})
} }
roundInfo := syncdata.RoundInfo{ return selections
Quote: quote.GetQuote(),
Sources: sources,
PlayerState: playerState,
}
return &roundInfo
} }

View File

@ -5,7 +5,7 @@ import (
) )
func (gm *Game) runRound() { func (gm *Game) runRound() {
state := gm.GetState() state, _ := gm.GetState()
if state != STATE_IDLE && state != STATE_PLAY { if state != STATE_IDLE && state != STATE_PLAY {
fmt.Println(fmt.Errorf("expected state \"IDLE\" | \"PLAY\" != \"%s\"", state)) fmt.Println(fmt.Errorf("expected state \"IDLE\" | \"PLAY\" != \"%s\"", state))
return return
@ -27,7 +27,7 @@ func (gm *Game) setupRound() {
defer gm.mu.Unlock() defer gm.mu.Unlock()
gm.round = Round{ gm.round = Round{
selection: make(map[string]string, 0), selections: make(map[string]string, 0),
} }
} }

View File

@ -1,6 +1,7 @@
package game package game
import ( import (
"fmt"
"sirlab.de/go/knyt/user" "sirlab.de/go/knyt/user"
) )
@ -13,5 +14,17 @@ func (gm *Game) updateSelection(usr *user.User, selection string) {
gm.mu.Lock() gm.mu.Lock()
defer gm.mu.Unlock() defer gm.mu.Unlock()
gm.round.selection[usr.GetId()] = selection if selection == PLAYER_SELECTION_NONE {
gm.round.selections[usr.GetId()] = PLAYER_SELECTION_NONE
return
}
for _, source := range gm.round.sources {
if source.id == selection {
gm.round.selections[usr.GetId()] = selection
return
}
}
fmt.Printf("invalid selection id \"%s\"\n", selection)
} }

View File

@ -16,6 +16,12 @@ const (
const ( const (
PHASE_NONE = "" PHASE_NONE = ""
PHASE_SELECT_QUOTE = "select-quote" PHASE_SELECT_QUOTE = "select-quote"
PHASE_REVEAL_START = "reveal-start"
)
const (
PLAYER_SELECTION_EMPTY = ""
PLAYER_SELECTION_NONE = "-"
) )
type playerInfo struct { type playerInfo struct {
@ -31,9 +37,9 @@ type Source struct {
} }
type Round struct { type Round struct {
quoteId string quoteId string
selection map[string]string selections map[string]string
sources []Source sources []Source
} }
type Game struct { type Game struct {

View File

@ -25,6 +25,7 @@ func main() {
mux.PrivateHandleFunc("/api/sync", app.SyncHandler) mux.PrivateHandleFunc("/api/sync", app.SyncHandler)
mux.PrivateHandleFunc("/api/startGame", app.StartGame) mux.PrivateHandleFunc("/api/startGame", app.StartGame)
mux.PrivateHandleFunc("/api/resetGame", app.ResetGame) mux.PrivateHandleFunc("/api/resetGame", app.ResetGame)
mux.PrivateHandleFunc("/api/continueGame", app.ContinueGame)
mux.PrivateHandleFunc("/api/saveSelection", app.SaveSelection) mux.PrivateHandleFunc("/api/saveSelection", app.SaveSelection)
// default handler // default handler

View File

@ -17,15 +17,10 @@ type SourceInfo struct {
Name string `json:"name"` Name string `json:"name"`
} }
type PlayerState struct {
Id string `json:"id"`
State string `json:"state"`
}
type RoundInfo struct { type RoundInfo struct {
Quote string `json:"quote"` Quote string `json:"quote"`
Sources []SourceInfo `json:"sources"` Sources []SourceInfo `json:"sources"`
PlayerState []PlayerState `json:"state"` Selections map[string]bool `json:"selections"`
} }
type GameInfo struct { type GameInfo struct {