diff --git a/gulpfile.js b/gulpfile.js index c7fbd24..bea3197 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -19,4 +19,12 @@ gulp.task('default', () => { .pipe(uglify()) .pipe(concat('draw.min.js')) .pipe(gulp.dest('static/js')) + + gulp.src('static/js/elo.js') + .pipe(babel({ + presets: ['env'] + })) + .pipe(uglify()) + .pipe(concat('elo.min.js')) + .pipe(gulp.dest('static/js')) }) diff --git a/main.go b/main.go index dbaa9e0..f887522 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,10 @@ func main() { c.HTML(200, "draw.tpl", gin.H{}) }) + router.GET("/elo", func (c *gin.Context) { + c.HTML(200, "elo.tpl", gin.H{}) + }) + // API v2 routers v2 := router.Group("/api/v2") { @@ -43,10 +47,12 @@ func main() { v2.GET("/tournaments", listTournaments) v2.POST("/tournaments", createTournament) - v2.GET("/last-tournament", lastTournament) + v2.GET("/tournaments/:id", getTournament) v2.PATCH("/tournaments/:id/matches/:match_id", updateMatchScore) v2.PATCH("/tournaments/:id/shuffle", shuffleMatch) + + //v2.GET("/members/:id/matches", getMemberMatches) } router.Run(":" + BILAC_PORT) diff --git a/members_controller.go b/members_controller.go index 484e5c2..688921b 100644 --- a/members_controller.go +++ b/members_controller.go @@ -1,91 +1,116 @@ package main import ( - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" - "./models" + "bilac/models" ) func listMembers(c *gin.Context) { - db := models.InitDB() - defer db.Close() + db := models.InitDB() + defer db.Close() - var mems []models.Member - db.Find(&mems) + sort := c.Query("sort") - c.JSON(200, mems) + var mems []models.Member + db.Order(sort).Find(&mems) + + c.JSON(200, mems) } func createMember(c *gin.Context) { - db := models.InitDB() - defer db.Close() - - var mem models.Member - c.Bind(&mem) - - if mem.Username == "" { - c.JSON(400, gin.H{"error": "Name not appropriate"}) - } else { - if err := db.Create(&mem).Error; err != nil { - c.JSON(500, gin.H{"error": err}) - } else { - c.JSON(201, mem) - } - } + db := models.InitDB() + defer db.Close() + + var mem models.Member + c.Bind(&mem) + + if mem.Username == "" { + c.JSON(400, gin.H{"error": "Name not appropriate"}) + } else { + if err := db.Create(&mem).Error; err != nil { + c.JSON(500, gin.H{"error": err}) + } else { + c.JSON(201, mem) + } + } } func showMember(c *gin.Context) { - db := models.InitDB() - defer db.Close() - - id := c.Params.ByName("id") - var mem models.Member - - db.First(&mem, id) - if mem.ID != 0 { - c.JSON(200, mem) - } else { - c.JSON(404, gin.H{"error": "Member not found"}) - } + db := models.InitDB() + defer db.Close() + + id := c.Params.ByName("id") + var mem models.Member + + db.First(&mem, id) + if mem.ID != 0 { + c.JSON(200, mem) + } else { + c.JSON(404, gin.H{"error": "Member not found"}) + } } func updateMember(c *gin.Context) { - db := models.InitDB() - defer db.Close() - - id := c.Params.ByName("id") - var mem models.Member - - db.First(&mem, id) - if mem.ID == 0 { - c.JSON(404, gin.H{"error": "Member not found"}) - } else { - var uMem models.Member - c.Bind(&uMem) - - if err := db.Model(&mem).Update("username", uMem.Username).Error; err != nil { - c.JSON(400, gin.H{"error": err}) - } else { - c.JSON(200, mem) - } - } + db := models.InitDB() + defer db.Close() + + id := c.Params.ByName("id") + var mem models.Member + + db.First(&mem, id) + if mem.ID == 0 { + c.JSON(404, gin.H{"error": "Member not found"}) + } else { + var uMem models.Member + c.Bind(&uMem) + + if err := db.Model(&mem).Update("username", uMem.Username).Error; err != nil { + c.JSON(400, gin.H{"error": err}) + } else { + c.JSON(200, mem) + } + } } func destroyMember(c *gin.Context) { - db := models.InitDB() - defer db.Close() - - id := c.Params.ByName("id") - var mem models.Member - - db.First(&mem, id) - if mem.ID != 0 { - if err := db.Delete(&mem).Error; err != nil { - c.JSON(500, gin.H{"error": err}) - } else { - c.Writer.WriteHeader(204) - } - } else { - c.JSON(404, gin.H{"error": "Member not found"}) - } + db := models.InitDB() + defer db.Close() + + id := c.Params.ByName("id") + var mem models.Member + + db.First(&mem, id) + if mem.ID != 0 { + if err := db.Delete(&mem).Error; err != nil { + c.JSON(500, gin.H{"error": err}) + } else { + c.Writer.WriteHeader(204) + } + } else { + c.JSON(404, gin.H{"error": "Member not found"}) + } } + +//func getMemberMatches(c *gin.Context) { +// db := models.InitDB() +// defer db.Close() +// +// id := c.Params.ByName("id") +// var mem models.Member +// db.First(&mem, id) +// +// var teamIDs []int +// db.Table("teams").Where("member1_id = ? OR member2_id = ?", id, id).Pluck("ID", &teamIDs) +// +// var matches []models.Match +// //var matches []struct { +// // ID int +// // +// //} +// db.Where("team1_id in (?)", teamIDs).Or("team2_id in (?)", teamIDs).Find(&matches) +// for _, match := range matches { +// +// } +// c.JSON(200, matches) +//} diff --git a/models/match.go b/models/match.go index 359e2fb..fc283d7 100644 --- a/models/match.go +++ b/models/match.go @@ -2,6 +2,8 @@ package models import ( "github.com/jinzhu/gorm" + "math" + //"fmt" ) type Match struct { @@ -28,3 +30,31 @@ func (match *Match) GetMatchInfo(newMatch Match) { match.Team1Score = newMatch.Team1Score match.Team2Score = newMatch.Team2Score } + +func (match Match) UpdateElo() { + db := InitDB() + defer db.Close() + + var team1, team2 Team + db.Model(match).Related(&team1, "Team1ID") + db.Model(match).Related(&team2, "Team2ID") + + elo1 := team1.AvgElo() + elo2 := team2.AvgElo() + + exp1 := 1 / float64(1 + math.Pow(10, (elo2 - elo1) / 500)) + exp2 := 1 / float64(1 + math.Pow(10, (elo1 - elo2) / 500)) + + var s1, s2 float64 + if match.Team1Score >= match.Team2Score { + s1 = float64(match.Team1Score) / float64(match.Team1Score + match.Team2Score) + s2 = 1 - s1 + } else { + s1 = 1 - float64(match.Team2Score) / float64(match.Team1Score + match.Team2Score) + s2 = 1 - s1 + } + + //fmt.Printf("%.3f : %.3f : %.3f : %.3f\n", exp1, s1, exp2, s2) + team1.UpdateElo(100 * (s1 - exp1)) + team2.UpdateElo(100 * (s2 - exp2)) +} \ No newline at end of file diff --git a/models/member.go b/models/member.go index 9447fd0..e449dc3 100644 --- a/models/member.go +++ b/models/member.go @@ -7,4 +7,13 @@ import ( type Member struct { gorm.Model Username string `gorm:"not null;unique" json:"username"` + Elo int `json:"elo" sql:"DEFAULT:1000"` } + +func (member Member) AddElo(amount int) { + db := InitDB() + defer db.Close() + + member.Elo += amount + db.Save(&member) +} \ No newline at end of file diff --git a/models/team.go b/models/team.go index e9d128c..1d9a44d 100644 --- a/models/team.go +++ b/models/team.go @@ -2,6 +2,8 @@ package models import ( "github.com/jinzhu/gorm" + //"fmt" + "math" ) type Team struct { @@ -26,7 +28,7 @@ type TeamRequest struct { } `json:"teams"` } -func GetPoint(x, y int) int { +func getPoint(x, y int) int { if x > y { return 3 } @@ -50,15 +52,53 @@ func (team Team) UpdateTeamScore() { if team.ID == match.Team1ID { team.GF += match.Team1Score team.GA += match.Team2Score - team.Points += GetPoint(match.Team1Score, match.Team2Score) + team.Points += getPoint(match.Team1Score, match.Team2Score) team.PlayedMatches += 1 } else if team.ID == match.Team2ID { team.GF += match.Team2Score team.GA += match.Team1Score - team.Points += GetPoint(match.Team2Score, match.Team1Score) + team.Points += getPoint(match.Team2Score, match.Team1Score) team.PlayedMatches += 1 } } team.GD = team.GF - team.GA db.Save(&team) } + +func (team Team) AvgElo() float64 { + db := InitDB() + defer db.Close() + + db.Preload("Member1").Preload("Member2").Find(&team, team.ID) + + return float64(team.Member1.Elo + team.Member2.Elo) / 2 +} + +func (team Team) UpdateElo(elo float64) { + db := InitDB() + defer db.Close() + + db.Preload("Member1").Preload("Member2").Find(&team, team.ID) + smallRatio := math.Min(float64(team.Member1.Elo), float64(team.Member2.Elo)) / + float64(team.Member1.Elo + team.Member2.Elo) + + var team1Ratio float64 + if elo >= 0 { + if team.Member1.Elo >= team.Member2.Elo { + team1Ratio = smallRatio + } else { + team1Ratio = 1 - smallRatio + } + } else { + if team.Member1.Elo >= team.Member2.Elo { + team1Ratio = 1 - smallRatio + } else { + team1Ratio = smallRatio + } + } + + //team1Ratio := 0.5 + //fmt.Printf("%.2f %.2f\n", elo, team1Ratio) + team.Member1.AddElo(int(2 * elo * team1Ratio)) + team.Member2.AddElo(int(2 * elo * (1 - team1Ratio))) +} \ No newline at end of file diff --git a/static/js/draw.js b/static/js/draw.js index 234e19e..fa20114 100644 --- a/static/js/draw.js +++ b/static/js/draw.js @@ -16,7 +16,11 @@ new Vue({ methods: { getMembers() { - axios.get('api/v2/members') + axios.get('api/v2/members', { + params: { + sort: "ID", + } + }) .then(res => { this.members = res.data }, err => { diff --git a/static/js/elo.js b/static/js/elo.js new file mode 100644 index 0000000..0edab69 --- /dev/null +++ b/static/js/elo.js @@ -0,0 +1,30 @@ +new Vue({ + el: "#tf", + delimiters: ['${', '}'], + + data: { + members: [], + }, + + mounted() { + let loader = document.getElementById("preloader") + loader.outerHTML = "" + + this.getMembers(); + }, + + methods: { + getMembers() { + axios.get('api/v2/members',{ + params: { + sort: "-elo" + } + }) + .then(res => { + this.members = res.data + }, err => { + console.log(err) + }) + } + } +}) diff --git a/static/js/table.js b/static/js/table.js index 5e55f97..7809452 100644 --- a/static/js/table.js +++ b/static/js/table.js @@ -3,7 +3,9 @@ new Vue({ delimiters: ['${', '}'], data: { + tourIDs: [], tourID: 0, + lastTourID: 0, matches: [], teams: [], team1Name: "", @@ -18,12 +20,32 @@ new Vue({ let loader = document.getElementById("preloader") loader.outerHTML = "" - this.getTournament() + this.getTourIDs() + }, + + watch: { + tourID() { + this.getTournament() + }, }, methods: { + getTourIDs() { + axios.get('/api/v2/tournaments') + .then(res => { + let ids = res.data.map(tour => tour.ID) + + this.$set(this, 'tourIDs', ids) + this.$set(this, 'tourID', ids[0]) + this.$set(this, 'lastTourID', ids[0]) + this.getTournament() + }, err => { + console.log(err) + }) + }, + getTournament() { - axios.get('/api/v2/last-tournament') + axios.get(`/api/v2/tournaments/${this.tourID}`) .then(res => { const data = res.data @@ -48,7 +70,7 @@ new Vue({ PlayedMatches: team.PlayedMatches, } }) - this.$set(this, 'tourID', data.ID) + this.$set(this, 'teams', teams) this.$set(this, 'matches', matches) diff --git a/tasks/elo_task.go b/tasks/elo_task.go new file mode 100644 index 0000000..8337d49 --- /dev/null +++ b/tasks/elo_task.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "bilac/models" +) + +func main() { + db := models.InitDB() + defer db.Close() + + // Reset elo of every member to 1000 + var members []models.Member + db.Find(&members) + + fmt.Printf("Reset all members's elo to 1000\n") + for _, mem := range members { + mem.Elo = 1000 + db.Save(&mem) + } + + var matches []models.Match + db.Find(&matches) + + fmt.Printf("Updating elo\n") + for _, match := range matches { + match.UpdateElo() + } +} \ No newline at end of file diff --git a/templates/elo.tpl b/templates/elo.tpl new file mode 100644 index 0000000..0215e27 --- /dev/null +++ b/templates/elo.tpl @@ -0,0 +1,63 @@ + + + + + + + + + Bilac + + + + + + + + + +
+
+
+
+ +
+

Members' Elo

+ +
+
+ + + + + + + + + + + + + + + + +
RankMemberElo
${ index + 1 }${ member.username }${ member.elo }
+
+
+
+
+ + Donate now for more future features! +
+ + + + +
+ + + + diff --git a/templates/table.tpl b/templates/table.tpl index 50033a0..c85b558 100644 --- a/templates/table.tpl +++ b/templates/table.tpl @@ -11,7 +11,7 @@ - + @@ -22,6 +22,16 @@

Foosball League Table

+

Tournament ${ tourID }

@@ -76,6 +86,7 @@ @@ -83,6 +94,7 @@
diff --git a/tours_controller.go b/tours_controller.go index 2b268f8..efc45cd 100644 --- a/tours_controller.go +++ b/tours_controller.go @@ -4,7 +4,7 @@ import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" - "./models" + "bilac/models" ) func listTournaments(c *gin.Context) { @@ -12,19 +12,21 @@ func listTournaments(c *gin.Context) { defer db.Close() var tours []models.Tournament - db.Order("CreatedAt").Find(&tours) + db.Order("created_at DESC").Find(&tours) c.JSON(200, tours) } -func lastTournament(c *gin.Context) { +func getTournament(c *gin.Context) { db := models.InitDB() defer db.Close() + id := c.Params.ByName("id") var tour models.Tournament - db.Order("created_at desc").Preload("Matches").Preload("Teams", func(db *gorm.DB) *gorm.DB { + + db.Preload("Matches").Preload("Teams", func(db *gorm.DB) *gorm.DB { return db.Order("teams.points DESC, teams.gd DESC, teams.played_matches") - }).Preload("Teams.Member1").Preload("Teams.Member2").First(&tour) + }).Preload("Teams.Member1").Preload("Teams.Member2").Find(&tour, id) c.JSON(200, tour) } @@ -110,6 +112,7 @@ func updateMatchScore(c *gin.Context) { team2 := match.Team2 team1.UpdateTeamScore() team2.UpdateTeamScore() + match.UpdateElo() c.JSON(201, match) } else { c.JSON(500, gin.H{"error": err})