tPrompt players for token to update their score - scoreboard - Interactive scoreboard for CTF-like games
(HTM) git clone git://git.z3bra.org/scoreboard.git
(DIR) Log
(DIR) Files
(DIR) Refs
---
(DIR) commit 2dcf67e2ab0f8c6918133d3a5bcb3c1dee86db98
(DIR) parent e74b632684c58ebb06b40f395409f7759a3ca9fb
(HTM) Author: Willy Goiffon <contact@z3bra.org>
Date: Mon, 5 Dec 2022 18:18:27 +0100
Prompt players for token to update their score
Diffstat:
M db.go | 107 ++++++++++++++-----------------
M main.go | 93 ++++++++++++++++++++++---------
M mkfile | 3 ++-
M playerbox.go | 8 ++++----
M ui.go | 13 ++++++++-----
5 files changed, 130 insertions(+), 94 deletions(-)
---
(DIR) diff --git a/db.go b/db.go
t@@ -26,104 +26,120 @@ const (
`
DB_FILL string = `
DELETE FROM score;
- INSERT INTO score(name,token,flag,score,ts) VALUES ('WGS', 'token0', 3, 600, 10);
- INSERT INTO score(name,token,flag,score,ts) VALUES ('DQK', 'token1', 5, 1337, 1);
- INSERT INTO score(name,token,flag,score,ts) VALUES ('VNM', 'token2', 5, 1000, 100);
- INSERT INTO score(name,token,flag,score,ts) VALUES ('PLR', 'token3', 5, 1000, 300);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('WGS', 'token0', 3, 302, 2);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('DQK', 'token1', 5, 1337, 20);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('VNM', 'token2', 5, 1000, 3);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('PLR', 'token3', 5, 1000, 2);
INSERT INTO score(name,token,flag,score,ts) VALUES ('UKN', 'token4', 2, 200, 200);
- INSERT INTO score(name,token,flag,score,ts) VALUES ('JFK', 'token5', 3, 600, 200);
- INSERT INTO score(name,token,flag,score,ts) VALUES ('NTM', 'token6', 4, 800, 100);
- INSERT INTO score(name,token,flag,score,ts) VALUES ('BOB', 'token7', 1, 200, 25);
- INSERT INTO score(name,token,flag,score,ts) VALUES ('AAA', 'token8', 1, 100, 13);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('JFK', 'token5', 3, 300, 200);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('AAA', 'token6', 1, 130, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('BBB', 'token7', 4, 400, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('CCC', 'token8', 4, 400, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('DDD', 'token9', 4, 407, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('EEE', 'tokenA', 4, 405, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('FFF', 'tokenB', 4, 401, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('GGG', 'tokenC', 4, 406, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('HHH', 'tokenD', 4, 408, 1670260445);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('III', 'tokenE', 4, 409, 1670260435);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('JJJ', 'tokenF', 4, 402, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('KKK', 'tokenG', 4, 400, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('LLL', 'tokenH', 4, 404, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('MMM', 'tokenI', 4, 403, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('NNN', 'tokenJ', 4, 400, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('OOO', 'tokenK', 4, 400, 1670260535);
+ INSERT INTO score(name,token,flag,score,ts) VALUES ('PPP', 'tokenL', 4, 400, 1670260535);
`
)
-func (a *Application) db_init(file string) error {
+func db_init(file string) (*sql.DB, error) {
var err error
+ var db *sql.DB
// open database
- a.db, err = sql.Open("sqlite", file)
+ db, err = sql.Open("sqlite", file)
if err != nil {
- return err
+ return nil, err
}
// create schema if needed
- _, err = a.db.Exec(DB_CREATE)
+ _, err = db.Exec(DB_CREATE)
if err != nil {
- return err
+ return nil, err
}
///*
- _, err = a.db.Exec(DB_FILL)
+ _, err = db.Exec(DB_FILL)
if err != nil {
panic(err)
}
//*/
- return nil
+ return db, nil
}
-func (a *Application) db_count() int {
+func db_count(db *sql.DB) int {
var count int
query := `SELECT count(id) FROM score;`
- row := a.db.QueryRow(query)
+ row := db.QueryRow(query)
row.Scan(&count)
return count
}
-func (a *Application) db_count_flag(flag int) int {
+func db_score_count(db *sql.DB, score, ts int) int {
var count int
query := `SELECT
count(id)
FROM score
WHERE
- flags >= ?
+ score >= ? AND
+ ts => ?
;`
- row := a.db.QueryRow(query, flag)
+ row := db.QueryRow(query, score, ts)
row.Scan(&count)
return count
}
-func (a *Application) db_checkname(nick string) bool {
+func db_flag_count(db *sql.DB, flag int) int {
var count int
query := `SELECT
count(id)
FROM score
WHERE
- name = ?
+ flag >= ?
;`
- row := a.db.QueryRow(query, nick)
+ row := db.QueryRow(query, flag)
row.Scan(&count)
- return (count > 0)
+ return count
}
-func (a *Application) db_rank(score int, ts int64) int {
- var rank int
+func db_id(db *sql.DB, nick string) bool {
+ var count int
query := `SELECT
count(id)
FROM score
WHERE
- score >= ?
+ name = ?
;`
- row := a.db.QueryRow(query, score)
- row.Scan(&rank)
- return rank + 1
+ row := db.QueryRow(query, nick)
+ row.Scan(&count)
+ return (count > 0)
}
-func (a *Application) db_ranked(offset, limit int) ([]Player, error) {
+func db_ranked_players(db *sql.DB, offset, limit int) ([]Player, error) {
query := `SELECT
id,name,token,flag,score
FROM score
ORDER BY
score DESC,
+ flag DESC,
ts ASC
LIMIT ?
OFFSET ?
;`
- rows, err := a.db.Query(query, limit, offset)
+ rows, err := db.Query(query, limit, offset)
if err != nil {
return nil, err
}
t@@ -131,7 +147,7 @@ func (a *Application) db_ranked(offset, limit int) ([]Player, error) {
players := make([]Player, 0)
for rows.Next() {
var p Player
- err := rows.Scan(&p.id, &p.name, &p.token, &p.flags, &p.score)
+ err := rows.Scan(&p.id, &p.name, &p.token, &p.flag, &p.score)
if err != nil {
return nil, err
}
t@@ -140,28 +156,3 @@ func (a *Application) db_ranked(offset, limit int) ([]Player, error) {
return players, nil
}
-
-func (a *Application) db_player(id int) *Player {
- var p Player
- query := `SELECT id,name,token,flag,score,ts FROM score WHERE id = ?`
-
- row := a.db.QueryRow(query, p.id)
- err := row.Scan(&p.id, &p.name, &p.token, &p.flags, &p.score)
- if err == sql.ErrNoRows {
- return nil
- }
-
- return &p
-}
-
-func (a *Application) db_save(p Player) int64 {
- query := `INSERT INTO score(name,token,flag,score,ts) VALUES(?,?,?,?,?);`
- r, err := a.db.Exec(query, p.name, p.token, p.flags, p.score, p.ts)
- if err != nil {
- panic(err)
- }
-
- id, _ := r.LastInsertId()
-
- return id
-}
(DIR) diff --git a/main.go b/main.go
t@@ -34,16 +34,8 @@ const (
DB string = "leaderboard.db"
)
-type Player struct {
- id int
- name string
- token string
- flags int
- score int
- ts int64
-}
-
type Application struct {
+ flag int
db *sql.DB
app *tview.Application
pages *tview.Pages
t@@ -54,11 +46,15 @@ type Application struct {
var flag_sha256 = [...]string {
"A",
+ "B",
+ "C",
+ "D",
+ "E"}
//"A6322C6522FB49959242670BC62E85A29ED7EB8CFCD87BE91521F0CA5EEBD198", // cookie.png
- "AFE502310D1EE1494770A46DEEED25CABB4B0CA70EFD0571C249C6BAA3728B46", // onion.png + zip
- "AC899E9F52E7194933E64D26800A609A83FF01826347FEA5ECDB2E420CAF1F43", // cream.png
- "8CB250A66D4301244699186CA723E11AFA806C64466789AB14B7027A2F928BF8", // egg.png
- "F6A4071C9C0DDCCB53FC1CDCA38E32CF10551F75AA48996A9341842A7EF2591B"} // salt.png
+ //"AFE502310D1EE1494770A46DEEED25CABB4B0CA70EFD0571C249C6BAA3728B46", // onion.png + zip
+ //"AC899E9F52E7194933E64D26800A609A83FF01826347FEA5ECDB2E420CAF1F43", // cream.png
+ //"8CB250A66D4301244699186CA723E11AFA806C64466789AB14B7027A2F928BF8", // egg.png
+ //"F6A4071C9C0DDCCB53FC1CDCA38E32CF10551F75AA48996A9341842A7EF2591B"} // salt.png
var cyboard = Application{
t@@ -88,18 +84,57 @@ func pageFlag() tview.Primitive {
return matched
})
input.SetDoneFunc(func(key tcell.Key) {
- switch flagid(input.GetText()) {
- case 0,1,2,3,4:
+ if cyboard.flag == 1 {
cyboard.pages.SwitchToPage("score")
- default:
- cyboard.app.Stop()
- fmt.Println("Incorrect flag")
}
})
return center(40, 1, input)
}
+func pageToken() tview.Primitive {
+ input := tview.NewInputField().
+ SetLabel("TOKEN ").
+ SetPlaceholder("").
+ SetFieldStyle(tcell.StyleDefault.Reverse(true)).
+ SetFieldWidth(28)
+
+ input.SetAcceptanceFunc(func(text string, ch rune) bool {
+ matched, err := regexp.Match(`^[a-zA-Z0-9]+$`, []byte(text))
+ if err != nil {
+ panic(err)
+ }
+ return matched
+ })
+ input.SetDoneFunc(func(key tcell.Key) {
+ _, err := cyboard.player.FromToken(input.GetText())
+ if err != nil {
+ cyboard.Popup("Error", "Invalid Token")
+ return
+ }
+
+ if cyboard.player.flag >= cyboard.flag {
+ cyboard.Popup("Error", "Flag already submitted")
+ 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")
+ })
+
+ return center(40, 1, input)
+}
+
func pageBoard() tview.Primitive {
cyboard.SetupFrame()
cyboard.DrawBoard()
t@@ -108,6 +143,7 @@ func pageBoard() tview.Primitive {
}
func main() {
+ var err error
cmd := "board"
flag.Parse()
t@@ -131,12 +167,17 @@ func main() {
tview.Styles.GraphicsColor = tcell.ColorDefault
tview.Styles.PrimaryTextColor = tcell.ColorDefault
- cyboard.db_init(DB)
+ cyboard.db, err = db_init(DB)
+ if err != nil {
+ panic(err)
+ }
+ cyboard.player.db = cyboard.db
defer cyboard.db.Close()
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 {
t@@ -144,24 +185,24 @@ func main() {
if (len(args) < 2) {
usage()
}
- switch flagid(args[1]) {
- case 0:
- cyboard.player.flags = 1
+ switch cyboard.flag = flagid(args[1]) + 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.db_count_flag(1)
+ n := cyboard.player.FlagRank()
if n < 10 {
cyboard.player.score += 10 - n
}
- rank := cyboard.db_rank(cyboard.player.score, cyboard.player.ts)
+ rank := cyboard.player.ScoreRank() + 1
cyboard.NewPlayer(rank)
cyboard.pages.SwitchToPage("board")
- //case 1,2,3,4:
- // pages.ShowPage("token")
+ case 2,3,4,5:
+ cyboard.pages.ShowPage("token")
default:
fmt.Println("Incorrect flag")
}
(DIR) diff --git a/mkfile b/mkfile
t@@ -1,4 +1,5 @@
GO = go
+SRC = `{find . -name '*.go'}
-scoreboard: main.go db.go playerbox.go util.go ui.go
+scoreboard: ${SRC}
${GO} build
(DIR) diff --git a/playerbox.go b/playerbox.go
t@@ -46,7 +46,7 @@ func manipulatebox(event *tcell.EventKey) *tcell.EventKey {
}
case tcell.KeyUp:
playerbox.char[playerbox.cur]++
- if playerbox.char[playerbox.cur] > len(charlist) {
+ if playerbox.char[playerbox.cur] >= len(charlist) {
playerbox.char[playerbox.cur] = 0
}
case tcell.KeyDown:
t@@ -70,8 +70,8 @@ func PlayerBoxName(p Player) *tview.TextView {
SetDoneFunc(func(key tcell.Key) {
if key == tcell.KeyEnter {
p.name = fmt.Sprintf("%3s", playerbox.name)
- if cyboard.db_checkname(p.name) == false {
- cyboard.db_save(p)
+ if ! p.Exists() {
+ p.Register()
cyboard.DrawBoard()
} else {
cyboard.Popup("Error", "Name already registered\nPlease pick another one")
t@@ -109,7 +109,7 @@ func PlayerBoxGrid(p Player, rank int) *tview.Grid {
SetRows(1).
AddItem(gridcell(rankstr), 0, 0, 1, 1, 0, 0, false).
AddItem(box, 0, 1, 1, 1, 0, 0, true).
- AddItem(gridcell(flag2str(p.flags)), 0, 2, 1, 1, 0, 0, false).
+ AddItem(gridcell(flag2str(p.flag)), 0, 2, 1, 1, 0, 0, false).
AddItem(playerbox.score, 0, 3, 1, 1, 0, 0, false)
return grid
(DIR) diff --git a/ui.go b/ui.go
t@@ -17,10 +17,7 @@ func BoardHeader() *tview.TextView {
func RankTable(offset, limit, rank int, fill bool) *tview.Table {
t := tview.NewTable()
- if limit < 0 {
- limit = cyboard.db_count()
- }
- players, err := cyboard.db_ranked(offset, limit)
+ players, err := db_ranked_players(cyboard.db, offset, limit)
if err != nil {
panic(err)
}
t@@ -31,7 +28,7 @@ func RankTable(offset, limit, rank int, fill bool) *tview.Table {
for i := 0; i < len(players); i++ {
p := players[i]
rankstr := fmt.Sprintf("%4s", humanize.Ordinal(rank + i + 1))
- flagstr := flag2str(p.flags)
+ flagstr := flag2str(p.flag)
scorestr := fmt.Sprintf("%4d", p.score)
t.SetCell(i, 0, newcell(rankstr))
t.SetCell(i, 1, newcell(p.name))
t@@ -72,6 +69,12 @@ func (a *Application) DrawBoard() {
AddItem(RankTable(0, -1, 0, true), BOARD_HEIGHT, 1, true)
}
+func (a *Application) HighlightBoard(line int) {
+ cyboard.board.Clear().
+ SetDirection(tview.FlexRow).
+ AddItem(RankTable(0, -1, 0, true).Select(line - 1, 0), BOARD_HEIGHT, 1, true)
+}
+
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)