Updates to match new primary repo.
This commit is contained in:
288
dashboard.go
288
dashboard.go
@@ -4,10 +4,9 @@ import (
|
||||
"bufio"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -19,32 +18,35 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DIMWIDTH = 1920
|
||||
DIMHEIGHT = 1080
|
||||
DASH_WIDTH = 1920
|
||||
DASH_HEIGHT = 1080
|
||||
|
||||
COLOR_GRAD_TOP = 0xff887c
|
||||
COLOR_GRAD_BOTTOM = 0xb6325f
|
||||
COLOR_GRAD_ETOP = 0xffcc33
|
||||
COLOR_GRAD_EBOTTOM = 0xcc9933
|
||||
//color codes
|
||||
DASH_COLOR_GRAD_TOP = 0x9601ce //0xff887c
|
||||
DASH_COLOR_GRAD_BOTTOM = 0x4b007a //0xb6325f
|
||||
DASH_COLOR_GRAD_ETOP = 0xffcc33
|
||||
DASH_COLOR_GRAD_EBOTTOM = 0xcc9933
|
||||
DASH_COLOR_GRAD_24TOP = 0xd42518
|
||||
DASH_COLOR_GRAD_24BOT = 0x911416
|
||||
|
||||
SCORE_UPDATE_INTERVAL = 60 * 60 * 5 //in seconds, 60 updates per second, 60 seconds per minute, 5 minutes
|
||||
SCORE_VERTICAL_OFFSET = 40 //pixels
|
||||
REWARD_SCALE = 5
|
||||
TIMER_UPDATE_RATE = 3 //updates per frame
|
||||
WINNER_CHARACTER_SPACING = 150
|
||||
TIMER_XOFFSET_DIST = 500
|
||||
POOMOJI_SIN_SCALER = 10
|
||||
DASH_SCALE_FACTOR = 3 //pixels per team-points on results bar
|
||||
DASH_ANIMATIONS_PER_CYCLE = 12 //our effective animation rate
|
||||
//intervals and rates
|
||||
DASH_SCORE_UPDATE_INTERVAL = 60 * 60 * 5 //in seconds, 60 updates per second, 60 seconds per minute, 5 minutes
|
||||
DASH_TIMER_UPDATE_RATE = 3 //updates per frame
|
||||
DASH_ANIMATIONS_PER_CYCLE = 12 //our effective animation rate
|
||||
|
||||
//various textual offset
|
||||
DASH_TIMESTR_XOFFSET = 50
|
||||
DASH_TIMESTR_YOFFSET = 40
|
||||
DASH_DEBUG_OFFSET = 50
|
||||
DASH_WINSTR_XOFFSET = 80
|
||||
DASH_WINSTR_YOFFSET = 55
|
||||
|
||||
MAX_DEBUG_TARGET = 200
|
||||
//offsets and scalers
|
||||
DASH_TIMESTR_XOFFSET = 50
|
||||
DASH_TIMESTR_YOFFSET = 40
|
||||
DASH_WINSTR_XOFFSET = 80
|
||||
DASH_WINSTR_YOFFSET = 55
|
||||
DASH_SCORE_VERTICAL_OFFSET = 40 //pixels
|
||||
DASH_REWARD_SCALE = 5
|
||||
DASH_WINNER_CHARACTER_SPACING = 150
|
||||
DASH_TIMER_XOFFSET_DIST = 500
|
||||
DASH_POOMOJI_SIN_SCALER = 10
|
||||
DASH_SCALE_FACTOR = 3 //pixels per team-points on results bar
|
||||
DASH_DEBUG_OFFSET = 50
|
||||
DASH_MAX_DEBUG_TARGET = 200
|
||||
)
|
||||
|
||||
type TeamData struct {
|
||||
@@ -60,6 +62,7 @@ type Dashboard struct {
|
||||
WindowTitle string
|
||||
Width int
|
||||
Height int
|
||||
settings *DashSettings
|
||||
|
||||
//timer and animation related
|
||||
tick int
|
||||
@@ -68,9 +71,14 @@ type Dashboard struct {
|
||||
expired bool
|
||||
aps int //animation-cycles per second
|
||||
endcondition bool
|
||||
final24 bool
|
||||
finalready bool
|
||||
|
||||
//dashboard elements and backgrounds
|
||||
gradientImage *image.RGBA
|
||||
gradient *Gradient
|
||||
|
||||
//gradientImage *image.RGBA
|
||||
|
||||
gradColorBottom color.RGBA
|
||||
gradColorTop color.RGBA
|
||||
reward *Reward
|
||||
@@ -91,13 +99,35 @@ func (d *Dashboard) Update() error {
|
||||
|
||||
//perform animation rate update check
|
||||
d.UpdateAnimationRate()
|
||||
d.UpdateTimer()
|
||||
d.UpdateScoreboardAsync()
|
||||
d.HandleInputs()
|
||||
|
||||
d.tick++
|
||||
return nil
|
||||
}
|
||||
|
||||
// instantaneous inputs, bypassing timers
|
||||
func (d *Dashboard) HandleInputs() {
|
||||
if d.expired {
|
||||
return
|
||||
}
|
||||
|
||||
/********** instantaneous score updates, bypassing timer bypass timer *****************/
|
||||
//END IMMEDIATELY
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyE) {
|
||||
d.deadline = time.Now()
|
||||
}
|
||||
|
||||
//24 HOURS REMAINING MODE
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyR) {
|
||||
//find the time.Time just under 24 hours from now, then use this to compute delta
|
||||
//between original deadline and new pre-24hour time, finally subtracting this duration
|
||||
//from the original deadline to arrive at our new, just shy of 24hour deadline
|
||||
timePlus24 := time.Now().Add(24*time.Hour + 5*time.Second)
|
||||
newoffset := d.deadline.Sub(timePlus24)
|
||||
d.deadline = d.deadline.Add(-newoffset)
|
||||
}
|
||||
|
||||
//GENERATE RANDOM SCORES
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyP) {
|
||||
d.leaderboard.RandomizeTeamTargets()
|
||||
@@ -106,49 +136,71 @@ func (d *Dashboard) Update() error {
|
||||
|
||||
//SET ALL TEAMS TO PREDETERMINED VALUE
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyL) {
|
||||
d.leaderboard.SetAllTargets(MAX_DEBUG_TARGET)
|
||||
d.leaderboard.SetAllTargets(DASH_MAX_DEBUG_TARGET)
|
||||
d.leaders = d.leaderboard.GetLeaders()
|
||||
}
|
||||
/**************************************************************************************/
|
||||
|
||||
//perform timer calculation
|
||||
if d.tick%TIMER_UPDATE_RATE == 0 {
|
||||
d.timeleft = time.Until(d.deadline)
|
||||
//CHOOSE AND SET NEW RANDOM GRADIENT COLORS FOR BG
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyPeriod) {
|
||||
d.gradient.SetColors(HexToRGBA(int(rand.Uint32())), HexToRGBA(int(rand.Uint32())))
|
||||
}
|
||||
|
||||
//reload scores in new thread
|
||||
if d.tick%SCORE_UPDATE_INTERVAL == 0 {
|
||||
//TRY AND RECONNECT TO JIRA
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyJ) {
|
||||
d.issuereader.Reconnect()
|
||||
if d.issuereader.IsAvailable() {
|
||||
go d.UpdateScoreboardAsync()
|
||||
}
|
||||
}
|
||||
|
||||
//FORCE DROP JIRA CONNECTION
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyD) {
|
||||
d.issuereader.DropClient()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (d *Dashboard) UpdateTimer() {
|
||||
//perform timer calculation if we're at the correct update interval
|
||||
if d.tick%DASH_TIMER_UPDATE_RATE == 0 {
|
||||
//except don't do it on weekends (since we're using working days weekends don't count)
|
||||
isweekend := (time.Now().Weekday() == time.Sunday || time.Now().Weekday() == time.Saturday)
|
||||
if !isweekend {
|
||||
d.timeleft = time.Until(d.deadline)
|
||||
d.expired = d.timeleft.Seconds() <= 0 //set expiry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use thread to reload scores
|
||||
func (d *Dashboard) UpdateScoreboardAsync() {
|
||||
if d.tick%DASH_SCORE_UPDATE_INTERVAL == 0 && !d.endcondition {
|
||||
go d.UpdateScoreboard()
|
||||
}
|
||||
|
||||
//have we expired?
|
||||
d.expired = d.timeleft.Seconds() <= 0
|
||||
d.tick++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// update score from jira actuals
|
||||
func (d *Dashboard) UpdateScoreboard() {
|
||||
|
||||
if d.issuereader == nil {
|
||||
if !d.issuereader.IsAvailable() {
|
||||
return
|
||||
}
|
||||
|
||||
if !d.expired {
|
||||
if !d.endcondition {
|
||||
scores := d.issuereader.RefreshScores()
|
||||
for k, v := range scores {
|
||||
d.leaderboard.UpdateScoreForTeam(k, v)
|
||||
//refresh could take a while, we may have actually ended in between
|
||||
if !d.endcondition {
|
||||
for k, v := range scores {
|
||||
d.leaderboard.UpdateScoreForTeam(k, v)
|
||||
}
|
||||
d.leaders = d.leaderboard.GetLeaders()
|
||||
}
|
||||
|
||||
d.leaders = d.leaderboard.GetLeaders()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dashboard) Draw(screen *ebiten.Image) {
|
||||
screen.WritePixels(d.gradientImage.Pix)
|
||||
screen.DrawImage(d.gradient.Scene, nil)
|
||||
d.DrawUpdate(screen)
|
||||
|
||||
//d.DrawDebug(screen)
|
||||
}
|
||||
|
||||
@@ -160,6 +212,22 @@ func (d *Dashboard) DrawUpdate(screen *ebiten.Image) {
|
||||
d.RenderTimer(screen)
|
||||
d.DrawLeaderboard(screen)
|
||||
}
|
||||
|
||||
d.DrawWarnings(screen)
|
||||
}
|
||||
|
||||
func (d *Dashboard) DrawWarnings(screen *ebiten.Image) {
|
||||
if d.tick%90 < 45 {
|
||||
var str string
|
||||
|
||||
if !d.issuereader.IsAvailable() {
|
||||
str = "OFFLINE MODE"
|
||||
}
|
||||
|
||||
if str != "" {
|
||||
text.Draw(screen, str, DashFont.Debug, DASH_TIMESTR_YOFFSET, DASH_TIMESTR_YOFFSET, color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dashboard) DrawLeaderboard(screen *ebiten.Image) {
|
||||
@@ -183,50 +251,55 @@ func (d *Dashboard) DrawReward(screen *ebiten.Image) {
|
||||
var ypos float64
|
||||
//compute relative position and offset for reward
|
||||
rpos := CharacterPosition{X: float64(d.Width) / 2, Y: float64(d.Height) / 2}
|
||||
xpos := rpos.X - float64(d.reward.Width())/2*REWARD_SCALE
|
||||
xpos := rpos.X - float64(d.reward.Width())/2*DASH_REWARD_SCALE
|
||||
|
||||
//animate the y position
|
||||
incr := d.tick * 2
|
||||
if incr < int(rpos.Y) {
|
||||
ypos = float64(incr)
|
||||
} else {
|
||||
ypos = rpos.Y + math.Sin((float64(d.tick)-rpos.Y)/(math.Pi*4))*POOMOJI_SIN_SCALER
|
||||
ypos = rpos.Y + math.Sin((float64(d.tick)-rpos.Y)/(math.Pi*4))*DASH_POOMOJI_SIN_SCALER
|
||||
}
|
||||
|
||||
//adjust y offset to center
|
||||
ypos = ypos - float64(d.reward.Width())/2*REWARD_SCALE
|
||||
ypos = ypos - float64(d.reward.Width())/2*DASH_REWARD_SCALE
|
||||
|
||||
//apply transformation
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Scale(REWARD_SCALE, REWARD_SCALE)
|
||||
op.GeoM.Scale(DASH_REWARD_SCALE, DASH_REWARD_SCALE)
|
||||
op.GeoM.Translate(xpos, ypos)
|
||||
screen.DrawImage(d.reward.Sprite, op)
|
||||
}
|
||||
|
||||
// draw winning characteres + scores
|
||||
func (d *Dashboard) DrawWinners(screen *ebiten.Image) {
|
||||
winnertext := GetWinnerText(len(d.leaders))
|
||||
text.Draw(screen, winnertext, DashFont.Debug, d.Width/2-DASH_WINSTR_XOFFSET, d.Height*2/3-DASH_WINSTR_YOFFSET, color.White)
|
||||
var i int = 0
|
||||
for _, team := range d.leaders {
|
||||
name := team.Name()
|
||||
numleaders := len(d.leaders)
|
||||
|
||||
mascot := d.winnerCharacters[i]
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
if numleaders > 0 {
|
||||
text.Draw(screen, GetWinnerText(numleaders), DashFont.Debug, d.Width/2-DASH_WINSTR_XOFFSET, d.Height*2/3-DASH_WINSTR_YOFFSET, color.White)
|
||||
var i int = 0
|
||||
for _, team := range d.leaders {
|
||||
name := team.Name()
|
||||
|
||||
totallen := len(d.leaders) * WINNER_CHARACTER_SPACING
|
||||
mascot := d.winnerCharacters[i]
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
|
||||
xpos := d.Width/2 - totallen/2 + i*WINNER_CHARACTER_SPACING
|
||||
ypos := d.Height * 2 / 3
|
||||
totallen := numleaders * DASH_WINNER_CHARACTER_SPACING
|
||||
|
||||
op.GeoM.Scale(2, 2)
|
||||
op.GeoM.Translate(float64(xpos), float64(ypos))
|
||||
screen.DrawImage(mascot.Sprite, op)
|
||||
text.Draw(screen, name, DashFont.Normal, xpos, ypos+150, color.Black)
|
||||
xpos := d.Width/2 - totallen/2 + i*DASH_WINNER_CHARACTER_SPACING
|
||||
ypos := d.Height * 2 / 3
|
||||
|
||||
str := fmt.Sprintf("%0.fPTS", team.GetTarget())
|
||||
text.Draw(screen, str, DashFont.Points, xpos, ypos+150+20, color.Black)
|
||||
i++
|
||||
op.GeoM.Scale(2, 2)
|
||||
op.GeoM.Translate(float64(xpos), float64(ypos))
|
||||
screen.DrawImage(mascot.Sprite, op)
|
||||
text.Draw(screen, name, DashFont.Normal, xpos, ypos+150, color.Black)
|
||||
|
||||
str := fmt.Sprintf("%0.fPTS", team.GetTarget())
|
||||
text.Draw(screen, str, DashFont.Points, xpos, ypos+150+20, color.Black)
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
text.Draw(screen, "EVERYONE LOSES", DashFont.Debug, d.Width/2-DASH_WINSTR_XOFFSET*3/2, d.Height*2/3-DASH_WINSTR_YOFFSET, color.White)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +316,7 @@ func (d *Dashboard) AnimateRewards() {
|
||||
// performs end condition settings exactly once per dashboard reset
|
||||
func (d *Dashboard) IniitializeEndCondition() {
|
||||
if !d.endcondition {
|
||||
FillGradient(d.gradientImage, d.Width, d.Height, HexToRGBA(COLOR_GRAD_ETOP), HexToRGBA(COLOR_GRAD_EBOTTOM))
|
||||
d.gradient.SetColors(HexToRGBA(DASH_COLOR_GRAD_ETOP), HexToRGBA(DASH_COLOR_GRAD_EBOTTOM))
|
||||
d.endcondition = true
|
||||
d.tick = 0
|
||||
|
||||
@@ -268,22 +341,36 @@ func (d *Dashboard) DrawDebug(screen *ebiten.Image) {
|
||||
func (d *Dashboard) RenderTimer(screen *ebiten.Image) {
|
||||
|
||||
hoursRaw := d.timeleft.Hours()
|
||||
days := math.Floor(float64(hoursRaw) / 24)
|
||||
|
||||
//ok this bit is messy due to our lame workweek tracking, if we've got less than 5 days left, use the raw day count
|
||||
//otherwise take into account business day tracking
|
||||
days := math.Floor(float64(hoursRaw) / 24) //total number of days, including weekends
|
||||
if days > 5 {
|
||||
days = float64(GetWeekDaysUntil(d.deadline)) //weekdays only (not including holidays)
|
||||
}
|
||||
|
||||
hours := int(hoursRaw) % 24
|
||||
minutes := int(d.timeleft.Minutes()) % 60 //minutes per hour
|
||||
seconds := int(d.timeleft.Seconds()) % 60 //seconds per minute
|
||||
|
||||
formattedTime := fmt.Sprintf("%3.f:%02d:%02d:%02d", days, hours, minutes, seconds)
|
||||
ypos := d.Height / 3
|
||||
xpos := d.Width/2 - TIMER_XOFFSET_DIST
|
||||
d.final24 = hoursRaw < 24
|
||||
if d.final24 && !d.finalready {
|
||||
d.leaderboard.Pinkify()
|
||||
d.gradient.SetColors(HexToRGBA(DASH_COLOR_GRAD_24TOP), HexToRGBA(DASH_COLOR_GRAD_24BOT))
|
||||
d.finalready = true
|
||||
}
|
||||
|
||||
text.Draw(screen, formattedTime, DashFont.PrincipleTimer, xpos, ypos, color.Black)
|
||||
text.Draw(screen, GetTimerFormatString(), DashFont.Normal, d.Width/2-DASH_TIMESTR_XOFFSET, ypos+DASH_TIMESTR_YOFFSET, color.Black)
|
||||
formattedTime := fmt.Sprintf("%.f:%02d:%02d:%02d", days, hours, minutes, seconds)
|
||||
ypos := d.Height / 3
|
||||
xpos := d.Width/2 - DASH_TIMER_XOFFSET_DIST
|
||||
|
||||
text.Draw(screen, formattedTime, DashFont.PrincipleTimer, xpos, ypos, color.White)
|
||||
text.Draw(screen, GetTimerFormatString(), DashFont.Normal, d.Width/2-DASH_TIMESTR_XOFFSET, ypos+DASH_TIMESTR_YOFFSET, color.White)
|
||||
}
|
||||
|
||||
func (d *Dashboard) DrawExpiration(screen *ebiten.Image) {
|
||||
if d.tick%90 < 45 {
|
||||
text.Draw(screen, GetCompletionText(), DashFont.Celebration, d.Width/2-600, d.Height/3, color.Black)
|
||||
text.Draw(screen, d.settings.Labels.Completion, DashFont.Celebration, d.Width/2-600, d.Height/3, color.Black)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,13 +385,12 @@ func (d *Dashboard) SetDimensions(w, h int) {
|
||||
if h > 0 {
|
||||
d.Height = h
|
||||
}
|
||||
|
||||
d.gradientImage = image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
FillGradient(d.gradientImage, d.Width, d.Height, d.gradColorTop, d.gradColorBottom)
|
||||
d.gradient.SetColors(d.gradColorTop, d.gradColorBottom)
|
||||
}
|
||||
|
||||
func (d *Dashboard) SetTime(t time.Time) {
|
||||
d.deadline = t
|
||||
d.timeleft = time.Until(d.deadline)
|
||||
}
|
||||
|
||||
// adjust animation rate based on keyboard input (up/down arrow keys)
|
||||
@@ -327,45 +413,55 @@ func (d *Dashboard) UpdateAnimationRate() {
|
||||
|
||||
// adds a jira issue reader to the dashboard - prompts for credentials
|
||||
func (d *Dashboard) AddIssueReader() {
|
||||
|
||||
r := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Jira Username: ")
|
||||
username, _ := r.ReadString('\n')
|
||||
|
||||
fmt.Print("Jira Password: ")
|
||||
pass, _ := term.ReadPassword(int(syscall.Stdin))
|
||||
|
||||
password := string(pass)
|
||||
d.issuereader = NewIssueReader(username, password)
|
||||
d.issuereader = NewIssueReader(username, password, d.settings)
|
||||
}
|
||||
|
||||
func NewDashboard() *Dashboard {
|
||||
func NewDashboard(s *DashSettings) *Dashboard {
|
||||
d := &Dashboard{
|
||||
tick: 0,
|
||||
WindowTitle: GetDashboardTitle(),
|
||||
gradColorTop: HexToRGBA(COLOR_GRAD_TOP),
|
||||
gradColorBottom: HexToRGBA(COLOR_GRAD_BOTTOM),
|
||||
WindowTitle: s.Labels.Title,
|
||||
gradColorTop: HexToRGBA(DASH_COLOR_GRAD_TOP),
|
||||
gradColorBottom: HexToRGBA(DASH_COLOR_GRAD_BOTTOM),
|
||||
aps: DASH_ANIMATIONS_PER_CYCLE,
|
||||
reward: NewReward(),
|
||||
endcondition: false,
|
||||
winnerCharacters: []*Character{},
|
||||
gradient: NewGradient(DASH_WIDTH, DASH_HEIGHT),
|
||||
final24: false,
|
||||
finalready: false,
|
||||
settings: s,
|
||||
}
|
||||
|
||||
//d.AddIssueReader()
|
||||
d.AddIssueReader()
|
||||
|
||||
teamnames := GetTeamNames()
|
||||
d.leaderboard = NewLeaderboard(ASB_WIDTH, (len(teamnames)+1)*ASB_HEIGHT+ASB_BUFFER, teamnames)
|
||||
d.SetTime(d.settings.Expiry)
|
||||
d.leaderboard = NewLeaderboard(ASB_WIDTH, (len(d.settings.Teams)+1)*ASB_HEIGHT+ASB_BUFFER, d.settings.Teams)
|
||||
d.leaderboard.SetScaleFactor(DASH_SCALE_FACTOR)
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func RunDashboard(t time.Time) {
|
||||
dash := NewDashboard()
|
||||
ebiten.SetWindowSize(ebiten.ScreenSizeInFullscreen())
|
||||
func RunDashboard(s *DashSettings) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
dash := NewDashboard(s)
|
||||
dash.SetDimensions(DASH_WIDTH, DASH_HEIGHT)
|
||||
|
||||
ebiten.SetWindowSize(ebiten.ScreenSizeInFullscreen())
|
||||
ebiten.SetWindowTitle(s.Labels.Title)
|
||||
|
||||
ebiten.SetWindowTitle(dash.WindowTitle)
|
||||
dash.SetDimensions(DIMWIDTH, DIMHEIGHT)
|
||||
ebiten.SetFullscreen(true)
|
||||
dash.SetTime(t)
|
||||
if err := ebiten.RunGame(dash); err != nil {
|
||||
log.Fatal(err)
|
||||
DashLogger.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user