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)
+}