Skip to content
This repository was archived by the owner on Oct 18, 2025. It is now read-only.

Commit 4e71c20

Browse files
committed
Add plotting and new functionallity playground folder
1 parent 30c0101 commit 4e71c20

25 files changed

Lines changed: 3158 additions & 11 deletions

File tree

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ RUN apk add --no-cache bash
66
COPY .bash_history /root/.bash_history
77
WORKDIR /var/controller
88

9+
# Add rendering dependencies
10+
RUN apk update && apk add go gcc libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev linux-headers mesa-dev libstdc++ mesa-utils mesa-dri-gallium && apk add --virtual build-dependencies build-base gcc wget git
11+
912
COPY . .
1013
RUN go mod download
1114
CMD ["go", "run", "cmd/main.go"]

cmd/cmd

32 MB
Binary file not shown.

cmd/main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66

77
"github.com/LiU-SeeGoals/controller/internal/demos"
8+
"github.com/LiU-SeeGoals/controller/internal/visualisation"
89
)
910

1011
func main() {
@@ -16,5 +17,8 @@ func main() {
1617
}
1718
}
1819

19-
demos.AoSimScenario()
20+
go demos.AoSimScenario()
21+
// Visualiser NEEDS to run in main thread
22+
vis := visualisation.GetVisualiser()
23+
vis.Run()
2024
}

go.mod

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,50 @@ require (
2121
// golang.org/x/net v1.2.3
2222
)
2323

24-
require golang.org/x/sys v0.17.0 // indirect
24+
require golang.org/x/sys v0.20.0 // indirect
2525

2626
require (
27+
fyne.io/fyne/v2 v2.5.5
28+
go.uber.org/zap v1.27.0
29+
gonum.org/v1/plot v0.10.1
30+
gopkg.in/natefinch/lumberjack.v2 v2.2.1
31+
)
32+
33+
require (
34+
fyne.io/systray v1.11.0 // indirect
35+
git.sr.ht/~sbinet/gg v0.3.1 // indirect
36+
github.com/BurntSushi/toml v1.4.0 // indirect
37+
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect
38+
github.com/davecgh/go-spew v1.1.1 // indirect
39+
github.com/fredbi/uri v1.1.0 // indirect
40+
github.com/fsnotify/fsnotify v1.7.0 // indirect
41+
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
42+
github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0 // indirect
43+
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
44+
github.com/go-fonts/liberation v0.3.0 // indirect
45+
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
46+
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
47+
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 // indirect
48+
github.com/go-pdf/fpdf v0.6.0 // indirect
49+
github.com/go-text/render v0.2.0 // indirect
50+
github.com/go-text/typesetting v0.2.0 // indirect
51+
github.com/godbus/dbus/v5 v5.1.0 // indirect
52+
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
53+
github.com/gopherjs/gopherjs v1.17.2 // indirect
54+
github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect
55+
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
56+
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
57+
github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
58+
github.com/pmezard/go-difflib v1.0.0 // indirect
59+
github.com/rymdport/portal v0.3.0 // indirect
60+
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
61+
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
62+
github.com/stretchr/testify v1.8.4 // indirect
63+
github.com/yuin/goldmark v1.7.1 // indirect
2764
go.uber.org/multierr v1.11.0 // indirect
28-
go.uber.org/zap v1.27.0 // indirect
29-
golang.org/x/net v0.21.0 // indirect
30-
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
65+
golang.org/x/image v0.18.0 // indirect
66+
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
67+
golang.org/x/net v0.25.0 // indirect
68+
golang.org/x/text v0.16.0 // indirect
69+
gopkg.in/yaml.v3 v3.0.1 // indirect
3170
)

go.sum

Lines changed: 730 additions & 6 deletions
Large diffs are not rendered by default.

internal/ai/slow_brain/slow_brain_ao.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import (
88

99
ai "github.com/LiU-SeeGoals/controller/internal/ai/activity"
1010
"github.com/LiU-SeeGoals/controller/internal/info"
11+
"github.com/LiU-SeeGoals/controller/internal/logger"
12+
vis "github.com/LiU-SeeGoals/controller/internal/visualisation"
13+
"gonum.org/v1/plot/plotter"
1114
)
1215

1316
type SlowBrainAo struct {
@@ -50,7 +53,9 @@ func (m *SlowBrainAo) run() {
5053
// - Function that returns indices for robots that should perform defense
5154
// Attacker: Chase ball, kick toward goal, turn to support when away from ball;
5255
// Support: Stand a bit away from attack so he can pass, turn into attacker when get ball
56+
robotPos := plotter.XYs{}
5357

58+
fig := vis.GetVisualiser().CreateEmptyPlotWindow()
5459
for {
5560
// No need for slow brain to be fast
5661
time.Sleep(1 * time.Millisecond)
@@ -64,6 +69,16 @@ func (m *SlowBrainAo) run() {
6469
defenders := []info.ID{0,1}
6570
attackers := []info.ID{3}
6671

72+
myRobotPos, err := gameInfo.State.GetTeam(m.team)[0].GetPosition()
73+
if err != nil {
74+
logger.Logger.Debugln("Big err")
75+
}
76+
77+
robotPos = append(robotPos, plotter.XY{X: myRobotPos.X, Y: myRobotPos.Y})
78+
p := vis.ScatterPlt(robotPos)
79+
p.Title.Text = fmt.Sprintf("Robot %v team %v", 0, m.team)
80+
fig.UpdatePlotWindow(p)
81+
6782
m.defense(defenders)
6883
m.attack(attackers)
6984

internal/visualisation/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
2+
# Visualiser
3+
4+
The visualiser is a local GUI that can run directly in go, currently (2025-04-06) used to create plots.
5+
But the GUI can be used to visualise more thigns if desired in the future.
6+
7+
As of (2025-04-06) visualiser consists of an interface with functions used to plot.
8+
The interface exists in case fayne (current GUI) is bad for some reason, and hopefully it is easy to switch
9+
10+
``` go
11+
func test() {
12+
// Initialize the global Visualiser
13+
vis := GetVisualiser()
14+
15+
// Start plot creation in separate goroutines
16+
go func() {
17+
points1 := plotter.XYs{
18+
{X: 0, Y: 0},
19+
{X: 1, Y: 1},
20+
{X: 2, Y: 4},
21+
{X: 3, Y: 9},
22+
}
23+
p := scatterPlt(points1)
24+
vis.CreatePlotWindow(p)
25+
}()
26+
27+
go func() {
28+
points2 := plotter.XYs{
29+
{X: 0, Y: 0},
30+
{X: 1, Y: 1},
31+
{X: 2, Y: 4},
32+
}
33+
p := linePlt(points2)
34+
fig := vis.CreatePlotWindow(p)
35+
for{
36+
val := points2[len(points2) - 1]
37+
newx := val.X + 1
38+
newy := newx * newx
39+
fmt.Println(val)
40+
41+
points2 = append(points2, plotter.XY{X: newx, Y: newy})
42+
43+
p := scatterPlt(points2)
44+
fig.UpdatePlotWindow(p)
45+
}
46+
}()
47+
48+
// Run the application main event loop
49+
vis.Run()
50+
}
51+
```
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package visualisation
2+
3+
import (
4+
"image"
5+
"sync"
6+
7+
fyne "fyne.io/fyne/v2"
8+
fyneapp "fyne.io/fyne/v2/app"
9+
fynecanvas "fyne.io/fyne/v2/canvas"
10+
fynecontainer "fyne.io/fyne/v2/container"
11+
"gonum.org/v1/plot"
12+
"gonum.org/v1/plot/plotter"
13+
"gonum.org/v1/plot/vg"
14+
"gonum.org/v1/plot/vg/draw"
15+
"gonum.org/v1/plot/vg/vgimg"
16+
)
17+
18+
// An example of how to use the visualiser can be seen in README.md contained in this package.
19+
20+
type Visualiser interface {
21+
// Create window with static plot
22+
CreatePlotWindow(p *plot.Plot) Figure
23+
24+
// Create empty plot, used in e.g. real-time plots
25+
CreateEmptyPlotWindow() Figure
26+
27+
// Start the GUI application, generally is blocking and
28+
// needs to run on main thread
29+
Run()
30+
}
31+
32+
type Figure interface{
33+
// A figure belongs to a specific visualiser
34+
// e.g. A FayneFigure is used to update plots in Fayne windows
35+
36+
UpdatePlotWindow(p *plot.Plot)
37+
}
38+
39+
type fayneVisualiser struct {
40+
app fyne.App
41+
mainWindow fyne.Window
42+
}
43+
44+
type fayneFigure struct {
45+
imageCanvas *fynecanvas.Image
46+
}
47+
48+
var (
49+
globalVis Visualiser
50+
globalVisOnce sync.Once
51+
)
52+
53+
/*
54+
Creates singleton visualiser that needs to run from main thread
55+
56+
Return another Visualiser to change the backend GUI
57+
*/
58+
func GetVisualiser() Visualiser {
59+
globalVisOnce.Do(func() {
60+
visApp := fyneapp.New()
61+
FayneVis := &fayneVisualiser{app: visApp}
62+
globalVis = FayneVis
63+
64+
// Create a persistent invisible window
65+
// One window needs to be open or main thread will die
66+
FayneVis.mainWindow = visApp.NewWindow("Persistent Window")
67+
FayneVis.mainWindow.Resize(fyne.NewSize(0, 0))
68+
FayneVis.mainWindow.SetFixedSize(true)
69+
FayneVis.mainWindow.SetCloseIntercept(func() {
70+
FayneVis.mainWindow.Hide()
71+
})
72+
FayneVis.mainWindow.Show() // Keep this window always open
73+
74+
hide := func(){
75+
// Hack to hide the main window on startup to keep app running....
76+
// time.Sleep(100 * time.Millisecond)
77+
FayneVis.mainWindow.Hide()
78+
}
79+
visApp.Lifecycle().SetOnStarted(hide)
80+
visApp.Lifecycle().SetOnEnteredForeground(hide)
81+
visApp.Lifecycle().SetOnExitedForeground(hide)
82+
})
83+
return globalVis
84+
}
85+
86+
func (vis *fayneVisualiser) Run() {
87+
vis.app.Run()
88+
}
89+
90+
func (fig *fayneFigure) UpdatePlotWindow(p *plot.Plot) {
91+
img := getPlotImg(p)
92+
93+
fig.imageCanvas.Image = img
94+
fynecanvas.Refresh(fig.imageCanvas)
95+
96+
}
97+
98+
// Creates an empty plotting window
99+
func (vis *fayneVisualiser) CreateEmptyPlotWindow() Figure {
100+
w := vis.app.NewWindow("")
101+
102+
imageCanvas := fynecanvas.NewImageFromImage(image.NewAlpha(image.Rect(0, 0, 600, 400)))
103+
imageCanvas.FillMode = fynecanvas.ImageFillOriginal
104+
105+
content := fynecontainer.NewCenter(imageCanvas)
106+
w.SetContent(content)
107+
w.Resize(fyne.NewSize(700, 500))
108+
w.Show()
109+
110+
return &fayneFigure{imageCanvas}
111+
}
112+
113+
// Create a plotting window with initial plot
114+
func (vis *fayneVisualiser) CreatePlotWindow(p *plot.Plot) Figure {
115+
w := vis.app.NewWindow(p.Title.Text)
116+
117+
imageCanvas := fynecanvas.NewImageFromImage(image.NewAlpha(image.Rect(0, 0, 600, 400)))
118+
imageCanvas.FillMode = fynecanvas.ImageFillOriginal
119+
120+
content := fynecontainer.NewCenter(imageCanvas)
121+
w.SetContent(content)
122+
w.Resize(fyne.NewSize(700, 500))
123+
w.Show()
124+
125+
// Get image of current plot
126+
img := getPlotImg(p)
127+
128+
imageCanvas.Image = img
129+
fynecanvas.Refresh(imageCanvas)
130+
return &fayneFigure{imageCanvas}
131+
}
132+
133+
func getPlotImg(p *plot.Plot) image.Image{
134+
135+
imgCanvas := vgimg.New(6*vg.Inch, 4*vg.Inch)
136+
p.Draw(draw.New(imgCanvas))
137+
return imgCanvas.Image()
138+
}
139+
140+
func ScatterPlt(points plotter.XYs) *plot.Plot {
141+
p := plot.New()
142+
p.Title.Text = "Real-Time Plot"
143+
p.X.Label.Text = "X-axis"
144+
p.Y.Label.Text = "Y-axis"
145+
146+
scatter, err := plotter.NewScatter(points)
147+
if err != nil {
148+
panic(err)
149+
}
150+
p.Add(scatter)
151+
152+
return p
153+
}
154+
155+
func LinePlt(points plotter.XYs) *plot.Plot {
156+
p := plot.New()
157+
p.Title.Text = "Real-Time Plot"
158+
p.X.Label.Text = "X-axis"
159+
p.Y.Label.Text = "Y-axis"
160+
161+
line, err := plotter.NewLine(points)
162+
if err != nil {
163+
panic(err)
164+
}
165+
p.Add(line)
166+
167+
return p
168+
}

0 commit comments

Comments
 (0)