tMake board editable in-place - scoreboard - Interactive scoreboard for CTF-like games
(HTM) git clone git://git.z3bra.org/scoreboard.git
(DIR) Log
(DIR) Files
(DIR) Refs
---
(DIR) commit 7689f88f24595ca9afe38cecab41bfc4e3c29473
(DIR) parent 54deea3d092677422021864f23df5cb091f0a0ec
(HTM) Author: Willy Goiffon <contact@z3bra.org>
Date: Mon, 5 Dec 2022 13:11:10 +0100
Make board editable in-place
Diffstat:
M db.go | 58 +++++++++++++++++++++++++------
M main.go | 173 +++++++------------------------
M mkfile | 2 +-
M playerbox.go | 12 ++++++------
A ui.go | 86 ++++++++++++++++++++++++++++++
M util.go | 2 +-
6 files changed, 178 insertions(+), 155 deletions(-)
---
(DIR) diff --git a/db.go b/db.go
t@@ -38,15 +38,39 @@ const (
`
)
-func db_count() int {
+
+func (a *Application) db_init(file string) error {
+ var err error
+
+ // open database
+ a.db, err = sql.Open("sqlite", file)
+ if err != nil {
+ return err
+ }
+
+ // create schema if needed
+ _, err = a.db.Exec(DB_CREATE)
+ if err != nil {
+ return err
+ }
+ /*
+ _, err = a.db.Exec(DB_FILL)
+ if err != nil {
+ panic(err)
+ }
+ //*/
+ return nil
+}
+
+func (a *Application) db_count() int {
var count int
query := `SELECT count(id) FROM score;`
- row := db.QueryRow(query)
+ row := a.db.QueryRow(query)
row.Scan(&count)
return count
}
-func db_rank(score int, ts int64) int {
+func (a *Application) db_rank(score int, ts int64) int {
var rank int
query := `SELECT
count(id)
t@@ -55,12 +79,26 @@ func db_rank(score int, ts int64) int {
score >= ?
;`
- row := db.QueryRow(query, score)
+ row := a.db.QueryRow(query, score)
row.Scan(&rank)
return rank + 1
}
-func db_ranked(offset, limit int) ([]Player, error) {
+func (a *Application) db_count_flag(flag int) int {
+ var count int
+ query := `SELECT
+ count(id)
+ FROM score
+ WHERE
+ flags >= ?
+ ;`
+
+ row := a.db.QueryRow(query, flag)
+ row.Scan(&count)
+ return count
+}
+
+func (a *Application) db_ranked(offset, limit int) ([]Player, error) {
query := `SELECT
id,name,token,flag,score
FROM score
t@@ -71,7 +109,7 @@ func db_ranked(offset, limit int) ([]Player, error) {
OFFSET ?
;`
- rows, err := db.Query(query, limit, offset)
+ rows, err := a.db.Query(query, limit, offset)
if err != nil {
return nil, err
}
t@@ -89,11 +127,11 @@ func db_ranked(offset, limit int) ([]Player, error) {
return players, nil
}
-func db_player(id int) *Player {
+func (a *Application) db_player(id int) *Player {
var p Player
query := `SELECT id,name,token,flag,score,ts FROM score WHERE id = ?`
- row := db.QueryRow(query, p.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
t@@ -102,9 +140,9 @@ func db_player(id int) *Player {
return &p
}
-func db_save(p Player) int64 {
+func (a *Application) db_save(p Player) int64 {
query := `INSERT INTO score(name,token,flag,score,ts) VALUES(?,?,?,?,?);`
- r, err := db.Exec(query, p.name, p.token, p.flags, p.score, p.ts)
+ r, err := a.db.Exec(query, p.name, p.token, p.flags, p.score, p.ts)
if err != nil {
panic(err)
}
(DIR) diff --git a/main.go b/main.go
t@@ -17,21 +17,20 @@ package main
import (
"flag"
"fmt"
- "math"
"os"
"regexp"
"time"
"database/sql"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
- "github.com/dustin/go-humanize"
//"golang.org/x/crypto/argon2"
_ "modernc.org/sqlite"
)
const (
- BOARD_SIZE int = 15
+ BOARD_WIDTH int = 26
+ BOARD_HEIGHT int = 15
DB string = "leaderboard.db"
)
t@@ -45,10 +44,12 @@ type Player struct {
}
type Application struct {
+ db *sql.DB
app *tview.Application
pages *tview.Pages
frame *tview.Grid
- board *tview.Grid
+ board *tview.Flex
+ player Player
}
var flag_sha256 = [...]string {
t@@ -59,20 +60,12 @@ var flag_sha256 = [...]string {
"8CB250A66D4301244699186CA723E11AFA806C64466789AB14B7027A2F928BF8", // egg.png
"F6A4071C9C0DDCCB53FC1CDCA38E32CF10551F75AA48996A9341842A7EF2591B"} // salt.png
-//var Application {
-// app: tview.NewApplication(),
-// frame: BoardFrame(26, 15),
-// pages: tview.NewPages(),
-// board: tview.NewTable(),
-//}
-var app = tview.NewApplication()
-var pages = tview.NewPages()
-var db *sql.DB
-var player = Player{
- score: 100,
- flags: 1,
- ts: time.Now().Unix(),
+var cyboard = Application{
+ app: tview.NewApplication(),
+ pages: tview.NewPages(),
+ frame: tview.NewGrid(),
+ board: tview.NewFlex(),
}
func usage() {
t@@ -80,41 +73,6 @@ func usage() {
os.Exit(0)
}
-func RankTable(t *tview.Table, offset, limit, rank int, fill bool) (*tview.Table) {
- players, err := db_ranked(offset, limit)
- if err != nil {
- panic(err)
- }
-
- t.SetSelectable(true, false).
- SetSelectedStyle(tcell.StyleDefault.Reverse(true))
-
- for i := 0; i < len(players); i++ {
- p := players[i]
- rankstr := fmt.Sprintf("%4s", humanize.Ordinal(rank + i))
- flagstr := flag2str(p.flags)
- scorestr := fmt.Sprintf("%4d", p.score)
- t.SetCell(i, 0, newcell(rankstr))
- t.SetCell(i, 1, newcell(p.name))
- t.SetCell(i, 2, newcell(flagstr))
- t.SetCell(i, 3, newcell(scorestr))
- }
-
- if fill == true {
- for i:=t.GetRowCount(); i<limit; i++ {
- rankstr := fmt.Sprintf("%4s", humanize.Ordinal(rank + i))
- flagstr := "....."
- scorestr := fmt.Sprintf("%4d", 0)
- t.SetCell(i, 0, newcell(rankstr).SetTextColor(tcell.ColorGray))
- t.SetCell(i, 1, newcell("AAA").SetTextColor(tcell.ColorGray))
- t.SetCell(i, 2, newcell(flagstr).SetTextColor(tcell.ColorGray))
- t.SetCell(i, 3, newcell(scorestr).SetTextColor(tcell.ColorGray))
- }
- }
-
- return t
-}
-
func pageFlag() tview.Primitive {
input := tview.NewInputField().
SetLabel("SHA256(flag) ").
t@@ -132,9 +90,9 @@ func pageFlag() tview.Primitive {
input.SetDoneFunc(func(key tcell.Key) {
switch flagid(input.GetText()) {
case 0,1,2,3,4:
- pages.SwitchToPage("score")
+ cyboard.pages.SwitchToPage("score")
default:
- app.Stop()
+ cyboard.app.Stop()
fmt.Println("Incorrect flag")
}
})
t@@ -142,68 +100,14 @@ func pageFlag() tview.Primitive {
return center(40, 1, input)
}
-func pageScore() tview.Primitive {
- board := tview.NewGrid().
- SetColumns(26).
- SetRows(1, BOARD_SIZE).
- SetBorders(true)
-
- header := tview.NewTextView().
- SetDynamicColors(true).
- SetTextAlign(tview.AlignRight).
- SetText("[::b] RANK NAME FLAGS SCORE ")
-
- //count := db_count()
- rank := db_rank(player.score, player.ts)
-
- grid := PlayerBoxGrid(player, rank)
-
- //if (count < BOARD_SIZE - 1) {
- t1 := tview.NewTable()
- t2 := tview.NewTable()
- RankTable(t1, 0, rank-1, 1, false)
- RankTable(t2, rank, BOARD_SIZE - rank, rank + 1, true)
- //}
-
- table := tview.NewFlex().
- SetDirection(tview.FlexRow).
- AddItem(t1, rank - 1, 0, false).
- AddItem(grid, 1, 0, true).
- AddItem(t2, BOARD_SIZE - rank - 1, 0, false)
-
- board.AddItem(header, 0, 0, 1, 1, 1, 26, false)
- board.AddItem(table, 1, 0, 1, 1, 1, 26, true)
-
- return center(30, 20, board)
-}
-
func pageBoard() tview.Primitive {
- bsize := int(math.Max(float64(db_count()), float64(BOARD_SIZE)))
- board := tview.NewGrid().
- SetColumns(26).
- SetRows(1, bsize).
- SetBorders(true)
-
- header := tview.NewTextView().
- SetDynamicColors(true).
- SetTextAlign(tview.AlignRight).
- SetText("[::b] RANK NAME FLAGS SCORE ")
-
- rank := db_rank(player.score, time.Now().Unix())
-
- table := tview.NewTable()
- RankTable(table, 0, bsize, 1, true)
- table.SetOffset(rank, 0)
-
- board.AddItem(header, 0, 0, 1, 1, 1, 26, false)
- board.AddItem(table, 1, 0, 1, 1, 1, 26, true)
+ cyboard.SetupFrame()
+ cyboard.DrawBoard()
- return center(30, 20, board)
+ return center(30, 20, cyboard.frame)
}
func main() {
- var err error
-
cmd := "board"
flag.Parse()
t@@ -227,51 +131,46 @@ func main() {
tview.Styles.GraphicsColor = tcell.ColorDefault
tview.Styles.PrimaryTextColor = tcell.ColorDefault
- // open database
- db, err = sql.Open("sqlite", DB)
- if err != nil {
- panic(err)
- }
- defer db.Close()
+ cyboard.db_init(DB)
+ defer cyboard.db.Close()
- // create schema if needed
- _, err = db.Exec(DB_CREATE)
- if err != nil {
- panic(err)
- }
- //_, err = db.Exec(DB_FILL)
- //if err != nil {
- // panic(err)
- //}
- pages.SetBackgroundColor(tcell.ColorDefault)
+ cyboard.pages.SetBackgroundColor(tcell.ColorDefault)
- pages.AddPage("flag", pageFlag(), true, false)
- pages.AddPage("score", pageScore(), true, false)
- pages.AddPage("board", pageBoard(), true, false)
+ cyboard.pages.AddPage("flag", pageFlag(), true, false)
+ cyboard.pages.AddPage("board", pageBoard(), true, false)
switch cmd {
- case "prompt":
- pages.ShowPage("flag")
case "flag":
if (len(args) < 2) {
usage()
}
switch flagid(args[1]) {
case 0:
- if db_count() < 10 {
- player.score += 10 - db_count()
+ cyboard.player.flags = 1
+ 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()
+ if n < 10 {
+ cyboard.player.score += 10 - n
}
- pages.SwitchToPage("score")
+
+ rank := cyboard.db_rank(cyboard.player.score, cyboard.player.ts)
+
+ cyboard.NewPlayer(rank)
+ cyboard.pages.SwitchToPage("board")
//case 1,2,3,4:
// pages.ShowPage("token")
default:
fmt.Println("Incorrect flag")
}
default:
- pages.SwitchToPage("board")
+ cyboard.DrawBoard()
+ cyboard.pages.SwitchToPage("board")
}
- if err := app.SetRoot(pages, true).EnableMouse(true).Run(); err != nil {
+ if err := cyboard.app.SetRoot(cyboard.pages, true).EnableMouse(true).Run(); err != nil {
panic(err)
}
}
(DIR) diff --git a/mkfile b/mkfile
t@@ -1,4 +1,4 @@
GO = go
-scoreboard: main.go db.go playerbox.go util.go
+scoreboard: main.go db.go playerbox.go util.go ui.go
${GO} build
(DIR) diff --git a/playerbox.go b/playerbox.go
t@@ -24,7 +24,7 @@ func boxtext (b PlayerBox) string {
for i:=0; i<3; i++ {
b.name[i] = charlist[b.char[i]]
if i == b.cur {
- str = fmt.Sprintf("%s[::r]%c[::-]", str, b.name[i])
+ str = fmt.Sprintf("%s[::rl]%c[::-]", str, b.name[i])
} else {
str = fmt.Sprintf("%s%c", str, b.name[i])
}
t@@ -65,18 +65,18 @@ func PlayerBoxName(p Player) *tview.TextView {
SetTextAlign(tview.AlignRight).
SetText(boxtext(playerbox)).
SetChangedFunc(func() {
- app.Draw()
+ cyboard.app.Draw()
}).
SetDoneFunc(func(key tcell.Key) {
if key == tcell.KeyEnter {
p.name = fmt.Sprintf("%3s", playerbox.name)
- db_save(p)
- pages.SwitchToPage("board")
+ cyboard.db_save(p)
+ cyboard.pages.SwitchToPage("board")
}
})
v.Focus(func(p tview.Primitive) {
- v.SetText(fmt.Sprintf("%4d ", player.score))
+ v.SetText(fmt.Sprintf("%4d ", cyboard.player.score))
})
v.SetInputCapture(manipulatebox)
t@@ -89,7 +89,7 @@ func PlayerBoxGrid(p Player, rank int) *tview.Grid {
return tview.NewTextView().
SetDynamicColors(true).
SetTextAlign(tview.AlignRight).
- SetText(fmt.Sprintf("[::l]%s",text))
+ SetText(fmt.Sprintf("[::b]%s",text))
}
rankstr := humanize.Ordinal(rank)
(DIR) diff --git a/ui.go b/ui.go
t@@ -0,0 +1,86 @@
+package main
+
+import (
+ "fmt"
+ "math"
+ "github.com/gdamore/tcell/v2"
+ "github.com/rivo/tview"
+ "github.com/dustin/go-humanize"
+)
+
+func BoardHeader() *tview.TextView {
+ return tview.NewTextView().
+ SetDynamicColors(true).
+ SetTextAlign(tview.AlignRight).
+ SetText("[::b] RANK NAME FLAGS SCORE ")
+}
+
+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)
+ if err != nil {
+ panic(err)
+ }
+
+ t.SetSelectable(true, false).
+ SetSelectedStyle(tcell.StyleDefault.Reverse(true))
+
+ for i := 0; i < len(players); i++ {
+ p := players[i]
+ rankstr := fmt.Sprintf("%4s", humanize.Ordinal(rank + i + 1))
+ flagstr := flag2str(p.flags)
+ scorestr := fmt.Sprintf("%4d", p.score)
+ t.SetCell(i, 0, newcell(rankstr))
+ t.SetCell(i, 1, newcell(p.name))
+ t.SetCell(i, 2, newcell(flagstr))
+ t.SetCell(i, 3, newcell(scorestr))
+ }
+
+ if fill == true {
+ bsize := int(math.Max(float64(BOARD_HEIGHT), float64(limit)))
+ for i:=t.GetRowCount(); i<bsize; i++ {
+ rankstr := fmt.Sprintf("%4s", humanize.Ordinal(rank + i + 1))
+ flagstr := "....."
+ scorestr := fmt.Sprintf("%4d", 0)
+ t.SetCell(i, 0, newcell(rankstr).SetTextColor(tcell.ColorGray))
+ t.SetCell(i, 1, newcell("AAA").SetTextColor(tcell.ColorGray))
+ t.SetCell(i, 2, newcell(flagstr).SetTextColor(tcell.ColorGray))
+ t.SetCell(i, 3, newcell(scorestr).SetTextColor(tcell.ColorGray))
+ }
+ }
+
+ return t
+}
+
+
+func (a *Application) SetupFrame() {
+ cyboard.frame.
+ SetColumns(BOARD_WIDTH).
+ SetRows(1, BOARD_HEIGHT).
+ SetBorders(true)
+
+ cyboard.frame.AddItem(BoardHeader(), 0, 0, 1, 1, 1, BOARD_WIDTH, false)
+ cyboard.frame.AddItem(cyboard.board, 1, 0, 1, 1, BOARD_HEIGHT, BOARD_WIDTH, true)
+}
+
+func (a *Application) DrawBoard() {
+ cyboard.board.Clear().
+ SetDirection(tview.FlexRow).
+ AddItem(RankTable(0, -1, 0, true), 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)
+ box := PlayerBoxGrid(cyboard.player, rank)
+
+ cyboard.board.Clear().
+ SetDirection(tview.FlexRow).
+ AddItem(t1, rank-1, 0, false).
+ AddItem(box, 1, 0, true).
+ AddItem(t2, BOARD_HEIGHT - rank, 0, false)
+}
+
(DIR) diff --git a/util.go b/util.go
t@@ -43,7 +43,7 @@ func flag2str(n int) string {
func newcell(text string) *tview.TableCell {
return tview.NewTableCell(text).
SetTransparency(true).
- SetSelectable(false).
+ SetSelectable(true).
SetAlign(tview.AlignRight).
SetExpansion(1)
}