Display results
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"vkane.cz/tinyquiz/pkg/model"
|
"vkane.cz/tinyquiz/pkg/model"
|
||||||
"vkane.cz/tinyquiz/pkg/model/ent"
|
"vkane.cz/tinyquiz/pkg/model/ent"
|
||||||
|
"vkane.cz/tinyquiz/pkg/rtcomm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (app *application) home(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func (app *application) home(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
@@ -148,6 +149,10 @@ func (app *application) nextQuestion(w http.ResponseWriter, r *http.Request, par
|
|||||||
app.serverError(w, err)
|
app.serverError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else if errors.Is(err, model.NoNextQuestion) {
|
||||||
|
app.rtClients.SendToAll(sessionId, rtcomm.StateUpdate{Results: true})
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
app.serverError(w, err)
|
app.serverError(w, err)
|
||||||
return
|
return
|
||||||
@@ -190,3 +195,36 @@ func (app *application) answer(w http.ResponseWriter, r *http.Request, params ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) resultsGeneral(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
|
type resultsData struct {
|
||||||
|
Results []model.PlayerResult
|
||||||
|
Session *ent.Session
|
||||||
|
Player *ent.Player
|
||||||
|
templateData
|
||||||
|
}
|
||||||
|
td := &resultsData{}
|
||||||
|
setDefaultTemplateData(&td.templateData)
|
||||||
|
|
||||||
|
var playerUid uuid.UUID
|
||||||
|
if uid, err := uuid.Parse(params.ByName("playerUid")); err == nil {
|
||||||
|
playerUid = uid
|
||||||
|
} else {
|
||||||
|
app.clientError(w, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if results, session, player, err := app.model.GetResults(playerUid, r.Context()); err == nil {
|
||||||
|
td.Results = results
|
||||||
|
td.Session = session
|
||||||
|
td.Player = player
|
||||||
|
} else if errors.Is(err, model.NoSuchEntity) {
|
||||||
|
app.clientError(w, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
app.serverError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.render(w, r, "results.page.tmpl.html", td)
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func main() {
|
|||||||
mux.GET("/game/:playerUid", app.game)
|
mux.GET("/game/:playerUid", app.game)
|
||||||
mux.POST("/game/:playerUid/rpc/next", app.nextQuestion)
|
mux.POST("/game/:playerUid/rpc/next", app.nextQuestion)
|
||||||
mux.POST("/game/:playerUid/answers/:choiceUid", app.answer)
|
mux.POST("/game/:playerUid/answers/:choiceUid", app.answer)
|
||||||
|
mux.GET("/results/:playerUid", app.resultsGeneral)
|
||||||
|
|
||||||
mux.GET("/ws/:playerUid", app.processWebSocket)
|
mux.GET("/ws/:playerUid", app.processWebSocket)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
"vkane.cz/tinyquiz/pkg/codeGenerator"
|
"vkane.cz/tinyquiz/pkg/codeGenerator"
|
||||||
"vkane.cz/tinyquiz/pkg/model/ent"
|
"vkane.cz/tinyquiz/pkg/model/ent"
|
||||||
@@ -305,6 +306,75 @@ func (m *Model) SaveAnswer(playerId uuid.UUID, choiceId uuid.UUID, now time.Time
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PlayerResult struct {
|
||||||
|
Player *ent.Player
|
||||||
|
place uint64
|
||||||
|
correct int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r PlayerResult) Points() int64 {
|
||||||
|
return r.correct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r PlayerResult) Place() uint64 {
|
||||||
|
return r.place
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) GetResults(playerId uuid.UUID, c context.Context) ([]PlayerResult, *ent.Session, *ent.Player, error) {
|
||||||
|
tx, err := m.c.BeginTx(c, &sql.TxOptions{
|
||||||
|
Isolation: sql.LevelRepeatableRead,
|
||||||
|
ReadOnly: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
defer tx.Commit()
|
||||||
|
|
||||||
|
s, err := tx.Session.Query().WithGame().Where(session.HasPlayersWith(player.ID(playerId))).Only(c)
|
||||||
|
if ent.IsNotFound(err) {
|
||||||
|
return nil, nil, nil, NoSuchEntity
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := tx.Player.Query().Where(player.ID(playerId)).Only(c)
|
||||||
|
if ent.IsNotFound(err) {
|
||||||
|
return nil, nil, nil, NoSuchEntity
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if players, err := tx.Player.Query().Where(player.HasSessionWith(session.ID(s.ID))).Where(player.Organiser(false)).Order(ent.Asc(player.FieldName)).WithAnswers(func(q *ent.AnswerQuery) { q.WithChoice() }).All(c); err == nil {
|
||||||
|
var results = make([]PlayerResult, 0, len(players))
|
||||||
|
for _, p := range players {
|
||||||
|
var res PlayerResult
|
||||||
|
res.Player = p
|
||||||
|
for _, a := range p.Edges.Answers {
|
||||||
|
if a.Edges.Choice.Correct {
|
||||||
|
res.correct++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results = append(results, res)
|
||||||
|
}
|
||||||
|
sort.SliceStable(results, func(i, j int) bool { return results[i].correct > results[j].correct }) // sort in reverse
|
||||||
|
if len(results) > 0 {
|
||||||
|
results[0].place = 1
|
||||||
|
}
|
||||||
|
var place uint64 = 2
|
||||||
|
for i := 1; i < len(results); i++ {
|
||||||
|
if results[i].Points() == results[i-1].Points() {
|
||||||
|
results[i].place = results[i-1].place
|
||||||
|
} else {
|
||||||
|
results[i].place = place
|
||||||
|
}
|
||||||
|
place++
|
||||||
|
}
|
||||||
|
return results, s, p, nil
|
||||||
|
} else {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const codeRandomPartLength uint8 = 3
|
const codeRandomPartLength uint8 = 3
|
||||||
|
|
||||||
func (m *Model) getCodeIncremental(c context.Context) (uint64, error) {
|
func (m *Model) getCodeIncremental(c context.Context) (uint64, error) {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package rtcomm
|
package rtcomm
|
||||||
|
|
||||||
type StateUpdate struct {
|
type StateUpdate struct {
|
||||||
Players []Player `json:"players"`
|
Players []Player `json:"players,omitempty"`
|
||||||
Question *QuestionUpdate `json:"question,omitempty"`
|
Question *QuestionUpdate `json:"question,omitempty"`
|
||||||
|
Results bool `json:"results,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Player struct {
|
type Player struct {
|
||||||
@@ -15,7 +16,7 @@ type QuestionUpdate struct {
|
|||||||
Answers []Answer `json:"answers"`
|
Answers []Answer `json:"answers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Answer struct{
|
type Answer struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,10 @@
|
|||||||
questionSection.appendChild(questionClone);
|
questionSection.appendChild(questionClone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('results' in data && data.results === true) {
|
||||||
|
window.location.pathname = "/results/" + encodeURIComponent(playerId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
|
|||||||
22
ui/html/results.page.tmpl.html
Normal file
22
ui/html/results.page.tmpl.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{{- template "base" . -}}
|
||||||
|
|
||||||
|
{{- define "additional-css" -}}
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
{{- define "additional-js" -}}
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
{{- define "header" }}
|
||||||
|
<h1>{{ .Session.Edges.Game.Name }} hrána {{ .Session.Started.Format "2006.01.02 15:04:05 MST" }}</h1>
|
||||||
|
<h2>{{ if .Player.Organiser }}Organizátor{{ else }}Hráč{{ end }} {{ .Player.Name }}</h2>
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
{{- define "main" }}
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{{ range .Results }}
|
||||||
|
<tr><td>{{ .Place }}. místo</td><td>{{ .Player.Name }}</td><td>{{ .Points }}b</td></tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{ end -}}
|
||||||
Reference in New Issue
Block a user