Skip to content

Commit

Permalink
Added support for max_rounds for X01
Browse files Browse the repository at this point in the history
  • Loading branch information
thordy committed Nov 30, 2024
1 parent e0de38b commit 25748c9
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 152 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- WLED support for venues
- TTS voice selection per venue
- Improved Tournament Generation
- Support for `Max Rounds` in `X01`
- Lots of new badges

#### Changed
Expand All @@ -19,6 +20,7 @@
#### Fixed
- Return `outshot_type` for `X01 Handicap`
- Correctly calculate match badges on match finish
- Remove any earned badges on leg finish undo

## [2.7.0] - 2023-09-12
#### Feature
Expand Down
1 change: 1 addition & 0 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var serveCmd = &cobra.Command{
router.HandleFunc("/leg/{id}/order", controllers.ChangePlayerOrder).Methods("PUT")
router.HandleFunc("/leg/{id}/warmup", controllers.StartWarmup).Methods("PUT")
router.HandleFunc("/leg/{id}/undo", controllers.UndoFinishLeg).Methods("PUT")
router.HandleFunc("/leg/{id}/finish", controllers.FinishLeg).Methods("PUT")

router.HandleFunc("/visit", controllers.AddVisit).Methods("POST")
router.HandleFunc("/visit/{id}/modify", controllers.ModifyVisit).Methods("PUT")
Expand Down
32 changes: 32 additions & 0 deletions controllers/leg_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"strconv"

"github.com/guregu/null"
"github.com/kcapp/api/data"
"github.com/kcapp/api/models"

Expand Down Expand Up @@ -340,3 +341,34 @@ func UndoFinishLeg(w http.ResponseWriter, r *http.Request) {
return
}
}

// FinishLeg will finalize a leg without a proper finish
func FinishLeg(w http.ResponseWriter, r *http.Request) {
SetHeaders(w)
params := mux.Vars(r)
legID, err := strconv.Atoi(params["id"])
if err != nil {
log.Println("Invalid id parameter")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

body := make(map[string]int)
err = json.NewDecoder(r.Body).Decode(&body)
if err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
winnerID, ok := body["winner_id"]
if !ok {
http.Error(w, "winner_id is required", http.StatusBadRequest)
return
}

err = data.FinishLeg(legID, winnerID, null.IntFrom(int64(winnerID)))
if err != nil {
log.Println("Unable to finish leg", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
253 changes: 133 additions & 120 deletions data/leg.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion data/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func NewMatch(match models.Match) (*models.Match, error) {
if params != nil {
outshotType = params.OutshotType.ID
}
_, err = tx.Exec("INSERT INTO leg_parameters (leg_id, outshot_type_id) VALUES (?, ?)", legID, outshotType)
_, err = tx.Exec("INSERT INTO leg_parameters (leg_id, outshot_type_id, max_rounds) VALUES (?, ?, ?)", legID, outshotType, params.MaxRounds)
if err != nil {
tx.Rollback()
return nil, err
Expand Down
10 changes: 7 additions & 3 deletions data/score.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func AddVisit(visit models.Visit) (*models.Visit, error) {
// Invalidate extra darts not thrown, and check if leg is finished
if matchType == models.X01 || matchType == models.X01HANDICAP {
visit.SetIsBust(players[visit.PlayerID].CurrentScore, leg.Parameters.OutshotType.ID)
isFinished = !visit.IsBust && visit.IsCheckout(players[visit.PlayerID].CurrentScore, leg.Parameters.OutshotType.ID)
isFinished = !visit.IsBust && visit.IsVisitCheckout(players[visit.PlayerID].CurrentScore, leg.Parameters.OutshotType.ID)
} else if matchType == models.SHOOTOUT {
isFinished = ((len(leg.Visits) + 1) * 3) >= (9 * len(leg.Players))
if isFinished {
Expand Down Expand Up @@ -198,7 +198,7 @@ func AddVisit(visit models.Visit) (*models.Visit, error) {
}
} else if matchType == models.ONESEVENTY {
visit.SetIsBust(players[visit.PlayerID].CurrentScore, models.OUTSHOTDOUBLE)
if visit.IsCheckout(players[visit.PlayerID].CurrentScore, models.OUTSHOTDOUBLE) {
if visit.IsVisitCheckout(players[visit.PlayerID].CurrentScore, models.OUTSHOTDOUBLE) {
players[visit.PlayerID].CurrentPoints.Int64++
}

Expand Down Expand Up @@ -274,7 +274,11 @@ func AddVisit(visit models.Visit) (*models.Visit, error) {
visit.IsBust)

if isFinished {
err = FinishLeg(visit)
winnerID, err := getLegWinner(leg, visit, matchType)
if err != nil {
return nil, err
}
err = FinishLeg(visit.LegID, visit.PlayerID, *winnerID)
if err != nil {
return nil, err
}
Expand Down
6 changes: 4 additions & 2 deletions data/statistics_x01.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,13 +547,15 @@ func CalculateX01Statistics(legID int) (map[int]*models.StatisticsX01, error) {
}
}

isCheckout := false // Check if player actually checked out or max rounds were reached
for _, visit := range visits {
player := playersMap[visit.PlayerID]
stats := statisticsMap[visit.PlayerID]

currentScore := player.CurrentScore
if visit.IsCheckout(currentScore, leg.Parameters.OutshotType.ID) {
if visit.IsVisitCheckout(currentScore, leg.Parameters.OutshotType.ID) {
stats.Checkout = null.IntFrom(int64(currentScore))
isCheckout = true
}
if visit.FirstDart.IsCheckoutAttempt(currentScore, 1, leg.Parameters.OutshotType.ID) {
stats.CheckoutAttempts++
Expand Down Expand Up @@ -607,7 +609,7 @@ func CalculateX01Statistics(legID int) (map[int]*models.StatisticsX01, error) {
}

for playerID, stats := range statisticsMap {
if playerID == winnerID {
if playerID == winnerID && isCheckout {
stats.CheckoutPercentage = null.FloatFrom(100 / float64(stats.CheckoutAttempts))

// When checking out, it might be done in 1, 2 or 3 darts, so make
Expand Down
29 changes: 16 additions & 13 deletions models/badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ func (b BadgeDoubleDouble) GetID() int {
return b.ID
}
func (b BadgeDoubleDouble) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}
visit := leg.GetLastVisit()
Expand All @@ -371,7 +371,7 @@ func (b BadgeTripleDouble) GetID() int {
return b.ID
}
func (b BadgeTripleDouble) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}
visit := leg.GetLastVisit()
Expand All @@ -382,7 +382,7 @@ func (b BadgeMadHouse) GetID() int {
return b.ID
}
func (b BadgeMadHouse) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}
visit := leg.GetLastVisit()
Expand Down Expand Up @@ -427,7 +427,7 @@ func (b BadgeBullseye) GetID() int {
return b.ID
}
func (b BadgeBullseye) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}
visit := leg.GetLastVisit()
Expand All @@ -439,7 +439,7 @@ func (b BadgeEasyAs123) GetID() int {
return b.ID
}
func (b BadgeEasyAs123) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}
visit := leg.GetLastVisit()
Expand All @@ -451,7 +451,7 @@ func (b BadgeCloseToPerfect) GetID() int {
return b.ID
}
func (b BadgeCloseToPerfect) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}
visit := leg.GetLastVisit()
Expand All @@ -473,7 +473,7 @@ func (b BadgeShanghaiCheckout) GetID() int {
return b.ID
}
func (b BadgeShanghaiCheckout) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}
visit := leg.GetLastVisit()
Expand All @@ -484,7 +484,7 @@ func (b BadgeTripleTrouble) GetID() int {
return b.ID
}
func (b BadgeTripleTrouble) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}

Expand All @@ -501,7 +501,7 @@ func (b BadgePerfection) GetID() int {
return b.ID
}
func (b BadgePerfection) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}

Expand All @@ -516,7 +516,7 @@ func (b BadgeChampagneShot) GetID() int {
return b.ID
}
func (b BadgeChampagneShot) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}

Expand All @@ -535,7 +535,7 @@ func (b BadgeYin) GetID() int {
return b.ID
}
func (b BadgeYin) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}

Expand Down Expand Up @@ -563,7 +563,7 @@ func (b BadgeYang) GetID() int {
return b.ID
}
func (b BadgeYang) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}

Expand Down Expand Up @@ -591,7 +591,7 @@ func (b BadgeZebra) GetID() int {
return b.ID
}
func (b BadgeZebra) Validate(leg *Leg) (bool, *int, *int) {
if !leg.IsX01() {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil, nil
}
winner := leg.GetLastVisit().PlayerID
Expand Down Expand Up @@ -713,6 +713,9 @@ func (b BadgeBeerGame) GetID() int {
return b.ID
}
func (b BadgeBeerGame) Validate(leg *Leg, players []*Player2Leg) (bool, *int) {
if !leg.IsX01() || !leg.IsLegCheckout() {
return false, nil
}
visit := leg.GetLastVisit()

for _, player := range players {
Expand Down
10 changes: 9 additions & 1 deletion models/leg.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ func (leg Leg) GetLastVisit() *Visit {

// IsX01 returns true if this leg is a X01 leg
func (leg Leg) IsX01() bool {
return leg.LegType.ID == X01
return leg.LegType.ID == X01 || leg.LegType.ID == X01HANDICAP
}

// GetFirstHitDart will return the first (non-Miss) dart for the given player
Expand All @@ -431,3 +431,11 @@ func (leg Leg) GetFirstHitDart(playerID int) *Dart {
}
return nil
}

func (leg Leg) IsLegCheckout() bool {
if !leg.IsX01() {
return false
}
lastVisit := leg.Visits[len(leg.Visits)-1]
return lastVisit.IsCheckout
}
3 changes: 2 additions & 1 deletion models/visit.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Visit struct {
SecondDart *Dart `json:"second_dart"`
ThirdDart *Dart `json:"third_dart"`
IsBust bool `json:"is_bust"`
IsCheckout bool `json:"is_checkout"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Count int `json:"count,omitempty"`
Expand Down Expand Up @@ -147,7 +148,7 @@ func (visit *Visit) SetIsBustAbove(currentScore int, targetScore int) {
}

// IsCheckout will check if the given visit is a checkout (remaining score is 0 and last dart thrown is a double)
func (visit Visit) IsCheckout(currentScore int, outshotTypeId int) bool {
func (visit Visit) IsVisitCheckout(currentScore int, outshotTypeId int) bool {
remaining := currentScore - visit.GetScore()
if remaining == 0 {
if outshotTypeId == OUTSHOTANY {
Expand Down
22 changes: 11 additions & 11 deletions models/visit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,35 @@ import (
"github.com/stretchr/testify/assert"
)

// TestIsCheckout will check that the given visit is checkout
func TestIsCheckout(t *testing.T) {
// TestIsVisitCheckout will check that the given visit is checkout
func TestIsVisitCheckout(t *testing.T) {
visit := Visit{FirstDart: &Dart{Value: null.IntFrom(20), Multiplier: 1},
SecondDart: &Dart{Value: null.IntFrom(20), Multiplier: 1},
ThirdDart: &Dart{Value: null.IntFrom(20), Multiplier: 1}}
assert.Equal(t, visit.IsCheckout(60, OUTSHOTANY), true, "should be checkout")
assert.Equal(t, visit.IsVisitCheckout(60, OUTSHOTANY), true, "should be checkout")

visit = Visit{FirstDart: &Dart{Value: null.IntFrom(20), Multiplier: 1},
SecondDart: &Dart{Value: null.IntFrom(20), Multiplier: 1},
ThirdDart: &Dart{Value: null.IntFrom(20), Multiplier: 1}}
assert.Equal(t, visit.IsCheckout(60, OUTSHOTDOUBLE), false, "should not be checkout")
assert.Equal(t, visit.IsVisitCheckout(60, OUTSHOTDOUBLE), false, "should not be checkout")

visit = Visit{FirstDart: &Dart{Value: null.IntFrom(20), Multiplier: 3}, SecondDart: &Dart{}, ThirdDart: &Dart{}}
assert.Equal(t, visit.IsCheckout(60, OUTSHOTMASTER), true, "should be checkout")
assert.Equal(t, visit.IsVisitCheckout(60, OUTSHOTMASTER), true, "should be checkout")

visit = Visit{FirstDart: &Dart{}, SecondDart: &Dart{Value: null.IntFrom(20), Multiplier: 2}, ThirdDart: &Dart{}}
assert.Equal(t, visit.IsCheckout(40, OUTSHOTDOUBLE), true, "should be checkout")
assert.Equal(t, visit.IsVisitCheckout(40, OUTSHOTDOUBLE), true, "should be checkout")
}

// TestIsCheckout_Master will check that the given visit is checkout
func TestIsCheckout_Master(t *testing.T) {
// TestIsVisitCheckout_Master will check that the given visit is checkout
func TestIsVisitCheckout_Master(t *testing.T) {
visit := Visit{FirstDart: &Dart{Value: null.IntFrom(20), Multiplier: 2},
SecondDart: &Dart{Value: null.IntFrom(10), Multiplier: 1},
ThirdDart: &Dart{Value: null.IntFrom(5), Multiplier: 2}}
assert.Equal(t, visit.IsCheckout(60, OUTSHOTMASTER), true, "should be checkout")
assert.Equal(t, visit.IsVisitCheckout(60, OUTSHOTMASTER), true, "should be checkout")

visit = Visit{FirstDart: &Dart{Value: null.IntFrom(14), Multiplier: 1}, SecondDart: &Dart{Value: null.IntFrom(7), Multiplier: 3}, ThirdDart: &Dart{}}
assert.Equal(t, visit.IsCheckout(35, OUTSHOTMASTER), true, "should be checkout")
assert.Equal(t, visit.IsVisitCheckout(35, OUTSHOTMASTER), true, "should be checkout")

visit = Visit{FirstDart: &Dart{Value: null.IntFrom(3), Multiplier: 3}, SecondDart: &Dart{}, ThirdDart: &Dart{}}
assert.Equal(t, visit.IsCheckout(9, OUTSHOTMASTER), true, "should be checkout")
assert.Equal(t, visit.IsVisitCheckout(9, OUTSHOTMASTER), true, "should be checkout")
}

0 comments on commit 25748c9

Please sign in to comment.