tSimplify interface and quit on error - scoreboard - Interactive scoreboard for CTF-like games
 (HTM) git clone git://git.z3bra.org/scoreboard.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
       ---
 (DIR) commit 955d6f3ab6a51e2bb139d30614ed7e5a932e14b6
 (DIR) parent 2dcf67e2ab0f8c6918133d3a5bcb3c1dee86db98
 (HTM) Author: Willy Goiffon <contact@z3bra.org>
       Date:   Tue,  6 Dec 2022 13:52:28 +0100
       
       Simplify interface and quit on error
       
       Diffstat:
         M main.go                             |      77 ++++++++-----------------------
         A player.go                           |     144 +++++++++++++++++++++++++++++++
         M playerbox.go                        |       4 ++--
         M ui.go                               |      34 +++++++++++++++++++++++++++++--
       
       4 files changed, 196 insertions(+), 63 deletions(-)
       ---
 (DIR) diff --git a/main.go b/main.go
       t@@ -23,7 +23,6 @@ import (
                "database/sql"
                "github.com/gdamore/tcell/v2"
                "github.com/rivo/tview"
       -        //"golang.org/x/crypto/argon2"
        
                _ "modernc.org/sqlite"
        )
       t@@ -69,29 +68,6 @@ func usage() {
                os.Exit(0)
        }
        
       -func pageFlag() tview.Primitive {
       -        input := tview.NewInputField().
       -                SetLabel("SHA256(flag) ").
       -                SetPlaceholder(" eg. 01BA4719…").
       -                SetFieldStyle(tcell.StyleDefault.Reverse(true)).
       -                SetFieldWidth(28)
       -
       -        input.SetAcceptanceFunc(func(text string, ch rune) bool {
       -                matched, err := regexp.Match(`^[a-fA-F0-9]+$`, []byte(text))
       -                if err != nil {
       -                        panic(err)
       -                }
       -                return matched
       -        })
       -        input.SetDoneFunc(func(key tcell.Key) {
       -                        if cyboard.flag == 1 {
       -                                cyboard.pages.SwitchToPage("score")
       -                        }
       -                })
       -
       -        return center(40, 1, input)
       -}
       -
        func pageToken() tview.Primitive {
                input := tview.NewInputField().
                        SetLabel("TOKEN ").
       t@@ -107,27 +83,17 @@ func pageToken() tview.Primitive {
                        return matched
                })
                input.SetDoneFunc(func(key tcell.Key) {
       -                        _, err := cyboard.player.FromToken(input.GetText())
       +                        err := cyboard.player.FromToken(input.GetText())
                                if err != nil {
       -                                cyboard.Popup("Error", "Invalid Token")
       +                                cyboard.Fatal(err)
                                        return
                                }
        
       -                        if cyboard.player.flag >= cyboard.flag {
       -                                cyboard.Popup("Error", "Flag already submitted")
       +                        err = cyboard.player.Submit(cyboard.flag)
       +                        if err != nil {
       +                                cyboard.Fatal(err)
                                        return
                                }
       -
       -                        cyboard.player.ts = time.Now().Unix()
       -                        cyboard.player.flag = cyboard.flag
       -                        cyboard.player.score += 100
       -
       -                        n := cyboard.player.FlagRank()
       -                        if n < 10 {
       -                                cyboard.player.score += 10 - n
       -                        }
       -
       -                        cyboard.player.Update()
                                cyboard.HighlightBoard(cyboard.player.ScoreRank())
                                cyboard.pages.SwitchToPage("board")
                        })
       t@@ -144,13 +110,6 @@ func pageBoard() tview.Primitive {
        
        func main() {
                var err error
       -        cmd := "board"
       -
       -        flag.Parse()
       -        args := flag.Args()
       -        if len(args) > 0 {
       -                cmd = args[0]
       -        }
        
                // Override default borders
                tview.Borders.HorizontalFocus  = tview.BoxDrawingsLightHorizontal
       t@@ -176,25 +135,24 @@ func main() {
        
                cyboard.pages.SetBackgroundColor(tcell.ColorDefault)
        
       -        cyboard.pages.AddPage("flag",   pageFlag(), true, false)
                cyboard.pages.AddPage("token",  pageToken(), true, false)
                cyboard.pages.AddPage("board",  pageBoard(), true, false)
        
       -        switch cmd {
       -        case "flag":
       -                if (len(args) < 2) {
       -                        usage()
       -                }
       -                switch cyboard.flag = flagid(args[1]) + 1; cyboard.flag {
       +        flag.Parse()
       +        args := flag.Args()
       +
       +        if len(args) > 1 {
       +                usage()
       +        } else if len(args) == 1 {
       +                switch cyboard.flag = flagid(args[0]) + 1; cyboard.flag {
                        case 1:
                                cyboard.player.flag = cyboard.flag
                                cyboard.player.score = 100
                                cyboard.player.ts = time.Now().Unix()
        
       -                        /* Bonus points for the first 10 players to get first flag */
       -                        n := cyboard.player.FlagRank()
       -                        if n < 10 {
       -                                cyboard.player.score += 10 - n
       +                        // Bonus points for the first player to submit a flag
       +                        if cyboard.player.FlagRank() == 0 {
       +                                cyboard.player.score += cyboard.flag * 10
                                }
        
                                rank := cyboard.player.ScoreRank() + 1
       t@@ -205,10 +163,11 @@ func main() {
                                cyboard.pages.ShowPage("token")
                        default:
                                fmt.Println("Incorrect flag")
       +                        return
                        }
       -        default:
       -                cyboard.DrawBoard()
       +        } else {
                        cyboard.pages.SwitchToPage("board")
       +                cyboard.DrawBoard()
                }
        
                if err := cyboard.app.SetRoot(cyboard.pages, true).EnableMouse(true).Run(); err != nil {
 (DIR) diff --git a/player.go b/player.go
       t@@ -0,0 +1,144 @@
       +// Copyright 2016 The Tcell Authors
       +//
       +// Licensed under the Apache License, Version 2.0 (the "License");
       +// you may not use file except in compliance with the License.
       +// You may obtain a copy of the license at
       +//
       +//    http://www.apache.org/licenses/LICENSE-2.0
       +//
       +// Unless required by applicable law or agreed to in writing, software
       +// distributed under the License is distributed on an "AS IS" BASIS,
       +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       +// See the License for the specific language governing permissions and
       +// limitations under the License.
       +
       +package main
       +
       +import (
       +        "errors"
       +        "fmt"
       +        "time"
       +        "database/sql"
       +        //"golang.org/x/crypto/argon2"
       +        "github.com/dustin/go-humanize"
       +
       +        _ "modernc.org/sqlite"
       +)
       +
       +type Player struct {
       +        db *sql.DB
       +        id int
       +        name string
       +        token string
       +        flag int
       +        score int
       +        ts int64
       +}
       +
       +func (p *Player) Register() int64 {
       +        query := `INSERT INTO score(name,token,flag,score,ts) VALUES(?,?,?,?,?);`
       +        r, err := p.db.Exec(query, p.name, p.token, p.flag, p.score, p.ts)
       +        if err != nil {
       +                panic(err)
       +        }
       +
       +        id, _ := r.LastInsertId()
       +
       +        return id
       +}
       +
       +func (p *Player) Update() int64 {
       +        query := `UPDATE score SET flag = ?, score = ?, ts = ? WHERE id = ?;`
       +        r, err := p.db.Exec(query, p.flag, p.score, p.ts, p.id)
       +        if err != nil {
       +                panic(err)
       +        }
       +
       +        id, _ := r.LastInsertId()
       +
       +        return id
       +}
       +
       +func (p *Player) ScoreRank() int {
       +        var count int
       +        query := `SELECT
       +          count(id)
       +          FROM score
       +          WHERE
       +            score >= ? AND
       +            ts <= ?
       +        ;`
       +
       +        row := p.db.QueryRow(query, p.score, p.ts)
       +        row.Scan(&count)
       +        return count
       +}
       +
       +func (p *Player) FlagRank() int {
       +        var count int
       +        query := `SELECT
       +          count(id)
       +          FROM score
       +          WHERE
       +            flag >= ?
       +        ;`
       +
       +        row := p.db.QueryRow(query, p.flag)
       +        row.Scan(&count)
       +        return count
       +}
       +
       +func (p *Player) Exists() bool {
       +        forbidden := []string{"KKK", "WGS"}
       +        for i := 0; i<len(forbidden); i++ {
       +                if p.name == forbidden[i] {
       +                        return true
       +                }
       +        }
       +
       +        var count int
       +        query := `SELECT
       +          count(id)
       +          FROM score
       +          WHERE
       +            name = ?
       +        ;`
       +
       +        row := p.db.QueryRow(query, p.name)
       +        row.Scan(&count)
       +        return (count > 0)
       +}
       +
       +func (p *Player) Submit(flag int) error {
       +        if flag <= p.flag {
       +                return errors.New("Flag already submitted")
       +        }
       +
       +        if flag != p.flag + 1 {
       +                return errors.New(fmt.Sprintf("Missing %s flag", humanize.Ordinal(p.flag + 1)))
       +        }
       +
       +        p.ts = time.Now().Unix()
       +        p.flag = flag
       +        p.score += 100
       +
       +        if p.FlagRank() == 0 {
       +                p.score += 10 * flag
       +        }
       +
       +        p.Update()
       +
       +        return nil
       +}
       +
       +func (p *Player) FromToken(token string) error {
       +        query := `SELECT id,name,token,flag,score,ts FROM score WHERE token = ?`
       +
       +        row := p.db.QueryRow(query, token)
       +        err := row.Scan(&p.id, &p.name, &p.token, &p.flag, &p.score, &p.ts)
       +        if err == sql.ErrNoRows {
       +                return errors.New("Unmatched token")
       +        }
       +
       +        return nil
       +}
 (DIR) diff --git a/playerbox.go b/playerbox.go
       t@@ -16,7 +16,7 @@ type PlayerBox struct {
                score *tview.TextView
        }
        
       -var charlist  = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 _.-!"
       +var charlist  = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-!"
        var playerbox = PlayerBox {name: []byte("AAA"), char: []int{0,0,0}, cur: 0}
        
        func boxtext (b PlayerBox) string {
       t@@ -74,7 +74,7 @@ func PlayerBoxName(p Player) *tview.TextView {
                                                p.Register()
                                                cyboard.DrawBoard()
                                        } else {
       -                                        cyboard.Popup("Error", "Name already registered\nPlease pick another one")
       +                                        cyboard.Popup("Error", "Player name unavailable\nPlease pick another one")
                                        }
                                }
                        })
 (DIR) diff --git a/ui.go b/ui.go
       t@@ -76,8 +76,18 @@ func (a *Application) HighlightBoard(line int) {
        }
        
        func (a *Application) NewPlayer(rank int) {
       -        t1 := RankTable(0, BOARD_HEIGHT, 0, false).SetSelectable(false, false)
       -        t2 := RankTable(rank - 1, BOARD_HEIGHT, rank, true).SetSelectable(false, false)
       +        var offset, t1_sz, t2_sz int
       +        if rank > BOARD_HEIGHT {
       +                offset = rank - BOARD_HEIGHT
       +                t1_sz = BOARD_HEIGHT - 1
       +                t2_sz = 0
       +        } else {
       +                offset = 0
       +                t1_sz = rank - 1
       +                t2_sz = BOARD_HEIGHT - rank - 1
       +        }
       +        t1 := RankTable(offset, BOARD_HEIGHT, t1_sz, false).SetSelectable(false, false)
       +        t2 := RankTable(rank - 1, BOARD_HEIGHT, t2_sz, true).SetSelectable(false, false)
                box := PlayerBoxGrid(cyboard.player, rank)
        
                cyboard.board.Clear().
       t@@ -106,3 +116,23 @@ func (a *Application) Popup(title, text string) {
        
                a.pages.AddAndSwitchToPage("popup", c, true)
        }
       +
       +func (a *Application) Fatal(err error) {
       +        p := tview.NewTextView().
       +                SetDynamicColors(true).
       +                SetTextAlign(tview.AlignCenter).
       +                SetText(fmt.Sprintf("[::b]%s", err)).
       +                SetDoneFunc(func(key tcell.Key) {
       +                        a.app.Stop()
       +                })
       +
       +        frame := tview.NewFrame(p).
       +                SetBorders(1, 0, 2, 2, 1, 1).
       +                AddText("Press RET", false, tview.AlignRight, tcell.ColorGray)
       +
       +        frame.SetBorder(true).SetTitle(" ERROR ")
       +
       +        c := center(30, 8, frame)
       +
       +        a.pages.AddAndSwitchToPage("fatal", c, true)
       +}