Implement creating new games

This commit is contained in:
Vojtěch Káně
2021-04-27 16:37:48 +02:00
parent ee91f3d7f8
commit 5f9ac3d4a9
7 changed files with 360 additions and 2 deletions

View File

@@ -0,0 +1,120 @@
package gameCreator
import (
"encoding/csv"
"errors"
"io"
"strconv"
"time"
)
func CreateTemplate(w io.Writer, questions uint64, choicesPerQuestion uint64) (retE error) {
csvW := csv.NewWriter(w)
defer func() {
csvW.Flush()
if err := csvW.Error(); err != nil && retE != nil {
retE = err
}
}()
for i := uint64(0); i < questions; i++ {
var length string
if i == 0 {
length = "10000"
}
if err := csvW.Write([]string{"Nadpis otázky", length, ""}); err != nil {
return err
}
for j := uint64(0); j < choicesPerQuestion; j++ {
var correct string
if j == 0 {
correct = "1"
}
if err := csvW.Write([]string{"", "Nadpis možnosti", correct}); err != nil {
return err
}
}
}
return nil
}
type Game struct {
Questions []Question
}
type Question struct {
Title string
Choices []Choice
Length uint64
}
type Choice struct {
Title string
Correct bool
}
var ErrTooManyQuestions = errors.New("there were questions above the limit")
var ErrTooManyChoices = errors.New("there were choices above the limit")
var ErrInvalidSyntax = errors.New("")
func Parse(r io.Reader, maxQuestions uint64, maxChoicesPerQuestion uint64) (Game, error) {
var g Game
var csvR = csv.NewReader(r)
csvR.FieldsPerRecord = 3
csvR.TrimLeadingSpace = true
var questions, choices uint64
for {
if row, err := csvR.Read(); err == nil {
if row[0] == "" && row[1] == "" && row[2] == "" {
continue
} else if row[0] == "" {
choices++
if questions == 0 {
return g, ErrInvalidSyntax
}
if choices > maxChoicesPerQuestion {
return g, ErrTooManyChoices
}
var correct bool
if row[2] == "1" {
correct = true
}
g.Questions[len(g.Questions)-1].Choices = append(g.Questions[len(g.Questions)-1].Choices, Choice{
Title: row[1],
Correct: correct,
})
} else {
questions++
choices = 0
if questions > maxQuestions {
return g, ErrTooManyQuestions
}
var length uint64
if row[1] != "" {
if l, err := strconv.ParseUint(row[1], 10, 64); err == nil {
length = l
} else {
return g, ErrInvalidSyntax
}
} else {
if questions > 1 {
length = g.Questions[len(g.Questions)-1].Length
} else {
length = uint64((10 * time.Second).Milliseconds())
}
}
g.Questions = append(g.Questions, Question{
Title: row[0],
Length: length,
})
}
} else if err == io.EOF {
break
} else if err == csv.ErrFieldCount {
return g, ErrInvalidSyntax
} else {
return g, err
}
}
return g, nil
}

View File

@@ -0,0 +1,66 @@
package gameCreator
import (
"bytes"
"reflect"
"strings"
"testing"
)
func TestCreateTemplate(t *testing.T) {
const expected = "Nadpis otázky,10000,\n,Nadpis možnosti,1\n,Nadpis možnosti,\n,Nadpis možnosti,\n,Nadpis možnosti,\n" +
"Nadpis otázky,,\n,Nadpis možnosti,1\n,Nadpis možnosti,\n,Nadpis možnosti,\n,Nadpis možnosti,\n" +
"Nadpis otázky,,\n,Nadpis možnosti,1\n,Nadpis možnosti,\n,Nadpis možnosti,\n,Nadpis možnosti,\n" +
"Nadpis otázky,,\n,Nadpis možnosti,1\n,Nadpis možnosti,\n,Nadpis možnosti,\n,Nadpis možnosti,\n" +
"Nadpis otázky,,\n,Nadpis možnosti,1\n,Nadpis možnosti,\n,Nadpis možnosti,\n,Nadpis možnosti,\n"
var actual bytes.Buffer
actual.Grow(len(expected))
if err := CreateTemplate(&actual, 5, 4); err != nil {
t.Fatalf("Unexpected error returned from CreateTemplate: %v", err)
}
if act := actual.String(); act != expected {
t.Fatalf("Wrong template generated. Expected:\n%s\n\nGot:\n%s\n", expected, act)
}
}
func TestParse(t *testing.T) {
const input = "H2O is,3000,\n,Gasoline,\n,Salt,\n,Water,1\n" +
"π is rational,,\n,Yes,\n,No,1\n" +
"IPv4 address length is,5000,\n,8b,\n,16b,\n,32b,1\n,64b,\n,128b,\n"
var expected = Game{
Questions: []Question{
{
Title: "H2O is",
Length: 3000,
Choices: []Choice{
{Title: "Gasoline"},
{Title: "Salt"},
{Title: "Water", Correct: true},
},
},
{
Title: "π is rational",
Length: 3000,
Choices: []Choice{
{Title: "Yes"},
{Title: "No", Correct: true},
},
},
{
Title: "IPv4 address length is",
Length: 5000,
Choices: []Choice{
{Title: "8b"},
{Title: "16b"},
{Title: "32b", Correct: true},
{Title: "64b"},
{Title: "128b"},
},
},
},
}
if g, err := Parse(strings.NewReader(input), 10, 10); err == nil && !reflect.DeepEqual(g, expected) {
t.Fatalf("Parse:\n\tActual: %#v\n\tExpected: %#v", g, expected)
} else if err != nil {
t.Fatalf("Unexpected error from Parse: %v", err)
}
}

View File

@@ -10,6 +10,7 @@ import (
"sort"
"time"
"vkane.cz/tinyquiz/pkg/codeGenerator"
"vkane.cz/tinyquiz/pkg/gameCreator"
"vkane.cz/tinyquiz/pkg/model/ent"
"vkane.cz/tinyquiz/pkg/model/ent/answer"
"vkane.cz/tinyquiz/pkg/model/ent/askedquestion"
@@ -387,6 +388,68 @@ func (m *Model) GetResults(playerId uuid.UUID, c context.Context) ([]PlayerResul
}
}
func (m *Model) CreateGame(game gameCreator.Game, name string, author string, c context.Context) (*ent.Game, error) {
tx, err := m.c.BeginTx(c, &sql.TxOptions{
Isolation: sql.LevelReadUncommitted,
})
if err != nil {
return nil, err
}
defer tx.Rollback()
var code []byte
if incremental, err := m.getCodeIncremental(c); err == nil {
if c, err := codeGenerator.GenerateRandomCode(incremental, codeRandomPartLength); err == nil {
code = c
} else {
return nil, err
}
} else {
return nil, err
}
g, err := tx.Game.Create().SetID(uuid.New()).SetCreated(time.Now()).SetName(name).SetAuthor(author).SetCode(string(code)).Save(c)
if err != nil {
return nil, err
}
var questions = make([]*ent.QuestionCreate, 0, len(game.Questions))
var questionIds = make([]uuid.UUID, 0, len(game.Questions))
var choicesCount uint
for i, q := range game.Questions {
var id = uuid.New()
var questionCreate = tx.Question.Create().SetID(id).SetGame(g).SetDefaultLength(q.Length).SetOrder(i + 1).SetTitle(q.Title)
questions = append(questions, questionCreate)
questionIds = append(questionIds, id)
choicesCount += uint(len(q.Choices))
}
if _, err := tx.Question.CreateBulk(questions...).Save(c); err != nil {
return nil, err
}
var choices = make([]*ent.ChoiceCreate, 0, choicesCount)
for i, q := range game.Questions {
for _, c := range q.Choices {
var choiceCreate = tx.Choice.Create().SetID(uuid.New()).SetTitle(c.Title).SetCorrect(c.Correct).SetQuestionID(questionIds[i])
choices = append(choices, choiceCreate)
}
}
if _, err := tx.Choice.CreateBulk(choices...).Save(c); err != nil {
return nil, err
}
return g, tx.Commit()
}
func (m *Model) GetGameWithQuestionsAndChoices(gameId uuid.UUID, c context.Context) (*ent.Game, error) {
if game, err := m.c.Game.Query().Where(game.ID(gameId)).WithQuestions(func(q *ent.QuestionQuery) { q.WithChoices() }).Only(c); err == nil {
return game, err
} else if ent.IsNotFound(err) {
return nil, NoSuchEntity
} else {
return nil, err
}
}
const codeRandomPartLength uint8 = 3
func (m *Model) getCodeIncremental(c context.Context) (uint64, error) {