Skip to content

Commit a3870be

Browse files
committed
[feature] Add a table for game events and a control ui to send them
1 parent b1ac96b commit a3870be

File tree

10 files changed

+367
-43
lines changed

10 files changed

+367
-43
lines changed

internal/app/controller/apiServer.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ type EventConsumer interface {
1616
OnNewEvent(event Event)
1717
}
1818

19+
type MessageWrapper struct {
20+
State *State `json:"state"`
21+
GameEvents *[]RefereeEvent `json:"gameEvents"`
22+
}
23+
1924
// WsHandler handles incoming web socket connections
2025
func (a *ApiServer) WsHandler(w http.ResponseWriter, r *http.Request) {
2126
u := websocket.Upgrader{
@@ -38,8 +43,8 @@ func (a *ApiServer) WsHandler(w http.ResponseWriter, r *http.Request) {
3843
a.listenForNewEvents(conn)
3944
}
4045

41-
func (a *ApiServer) PublishState(state State) {
42-
b, err := json.Marshal(state)
46+
func (a *ApiServer) PublishWrapper(wrapper MessageWrapper) {
47+
b, err := json.Marshal(wrapper)
4348
if err != nil {
4449
log.Println("Marshal error:", err)
4550
}
@@ -52,6 +57,16 @@ func (a *ApiServer) PublishState(state State) {
5257
}
5358
}
5459

60+
func (a *ApiServer) PublishState(state State) {
61+
wrapper := MessageWrapper{State: &state}
62+
a.PublishWrapper(wrapper)
63+
}
64+
65+
func (a *ApiServer) PublishGameEvents(gameEvents []RefereeEvent) {
66+
wrapper := MessageWrapper{GameEvents: &gameEvents}
67+
a.PublishWrapper(wrapper)
68+
}
69+
5570
func (a *ApiServer) disconnect(conn *websocket.Conn) {
5671
conn.Close()
5772
for i, c := range a.connections {

internal/app/controller/controller.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ func (r *GameController) OnNewEvent(event Event) {
6767
log.Println("Could not process event:", event, err)
6868
} else {
6969
r.publish(cmd)
70+
if cmd != nil {
71+
r.publishGameEvents()
72+
}
7073
}
7174
}
7275

@@ -131,6 +134,11 @@ func (r *GameController) publish(command *EventCommand) {
131134
r.Publisher.Publish(r.Engine.State, command)
132135
}
133136

137+
// publishGameEvents publishes the current list of game events
138+
func (r *GameController) publishGameEvents() {
139+
r.ApiServer.PublishGameEvents(r.Engine.GameEvents)
140+
}
141+
134142
// saveLatestState writes the current state into a file
135143
func (r *GameController) saveLatestState() {
136144
jsonState, err := json.MarshalIndent(r.Engine.State, "", " ")

internal/app/controller/engine.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Engine struct {
1414
config ConfigGame
1515
StateHistory []State
1616
TimeProvider func() time.Time
17+
GameEvents []RefereeEvent
1718
}
1819

1920
func NewEngine(config ConfigGame) (e Engine) {
@@ -55,9 +56,33 @@ func (e *Engine) Process(event Event) (*EventCommand, error) {
5556
if err == nil {
5657
e.StateHistory = append(e.StateHistory, *e.State)
5758
}
59+
if cmd != nil {
60+
e.LogCommand(cmd)
61+
}
5862
return cmd, err
5963
}
6064

65+
func (e *Engine) LogGameEvent(eventType GameEventType) {
66+
gameEvent := RefereeEvent{
67+
Timestamp: e.TimeProvider(),
68+
StageTime: e.State.StageTimeElapsed,
69+
Type: RefereeEventGameEvent,
70+
GameEventType: &eventType,
71+
}
72+
e.GameEvents = append(e.GameEvents, gameEvent)
73+
}
74+
75+
func (e *Engine) LogCommand(command *EventCommand) {
76+
gameEvent := RefereeEvent{
77+
Timestamp: e.TimeProvider(),
78+
StageTime: e.State.StageTimeElapsed,
79+
Type: RefereeEventCommand,
80+
Command: &command.Type,
81+
Team: command.ForTeam,
82+
}
83+
e.GameEvents = append(e.GameEvents, gameEvent)
84+
}
85+
6186
func (e *Engine) loadStages() {
6287
e.StageTimes = map[Stage]time.Duration{}
6388
for _, stage := range Stages {

internal/app/controller/gameEvents.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package controller
2+
3+
import "time"
4+
5+
type GameEventType string
6+
7+
type RefereeEventType string
8+
9+
const (
10+
RefereeEventCommand RefereeEventType = "command"
11+
RefereeEventGameEvent RefereeEventType = "gameEvent"
12+
)
13+
14+
type RefereeEvent struct {
15+
Timestamp time.Time `json:"timestamp"`
16+
StageTime time.Duration `json:"stageTime"`
17+
Type RefereeEventType `json:"type"`
18+
GameEventType *GameEventType `json:"gameEventType"`
19+
Command *RefCommand `json:"command"`
20+
Team *Team `json:"team"`
21+
Description *string `json:"description"`
22+
}

src/App.vue

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@
99
<h2>Yellow Team</h2>
1010
<TeamOverview class="team-views" team-color="Yellow"/>
1111
<ControlTeam class="team-views" team-color="Yellow"/>
12+
<ControlTeamEvent class="team-views" team-color="Yellow"/>
1213
</div>
13-
<div id="field">
14+
<div class="main-middle-container">
15+
<GameEvents/>
1416
<iframe src="http://localhost:8082/" frameborder="none"></iframe>
1517
</div>
1618
<div class="team-container">
1719
<h2>Blue Team</h2>
1820
<TeamOverview class="team-views" team-color="Blue"/>
1921
<ControlTeam class="team-views" team-color="Blue"/>
22+
<ControlTeamEvent class="team-views" team-color="Blue"/>
2023
</div>
2124
</div>
2225
<ControlMatch id="match-controls"/>
23-
<Debug id="debug"/>
2426
</div>
2527
</template>
2628

@@ -29,14 +31,16 @@
2931
import TeamOverview from "./components/team/TeamOverview";
3032
import ControlGeneral from "./components/control/ControlGeneral";
3133
import ControlTeam from "./components/control/ControlTeam";
32-
import Debug from "./components/Debug";
3334
import ControlMatch from "./components/control/ControlMatch";
35+
import ControlTeamEvent from "./components/control/ControlTeamEvent";
36+
import GameEvents from "./components/GameEvents";
3437
3538
export default {
3639
name: 'app',
3740
components: {
41+
GameEvents,
42+
ControlTeamEvent,
3843
ControlMatch,
39-
Debug,
4044
ControlTeam,
4145
ControlGeneral,
4246
TeamOverview,
@@ -86,11 +90,17 @@
8690
flex-grow: 1;
8791
}
8892
89-
#field {
93+
.main-middle-container {
94+
display: flex;
95+
flex-direction: column;
96+
flex-wrap: nowrap;
97+
justify-content: flex-start;
98+
align-content: stretch;
9099
flex-grow: 1;
91100
}
92101
93-
#field iframe {
102+
.main-middle-container iframe {
103+
flex-grow: 1;
94104
width: 100%;
95105
height: 100%;
96106
}

src/components/Debug.vue

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/components/GameEvents.vue

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<template>
2+
<div>
3+
<b-table striped hover small
4+
responsive="true"
5+
:sort-by.sync="sortBy"
6+
:sort-desc.sync="sortDesc"
7+
:per-page="perPage"
8+
:current-page="currentPage"
9+
:items="gameEvents"
10+
:fields="fields">
11+
<template slot="timestamp" slot-scope="data">
12+
{{formatTimestamp(data.item.timestamp)}}
13+
</template>
14+
<template slot="stageTime" slot-scope="data">
15+
<span v-format-ns-duration="data.item.stageTime"></span>
16+
</template>
17+
</b-table>
18+
<b-pagination size="sm"
19+
align="center"
20+
:total-rows="gameEvents.length"
21+
v-model="currentPage"
22+
:per-page="perPage">
23+
</b-pagination>
24+
</div>
25+
</template>
26+
27+
<script>
28+
import "../date.format";
29+
30+
export default {
31+
name: "GameEvents",
32+
data() {
33+
return {
34+
sortBy: 'timestamp',
35+
sortDesc: true,
36+
currentPage: 1,
37+
perPage: 5,
38+
fields: [
39+
{
40+
key: 'timestamp',
41+
sortable: true
42+
},
43+
{
44+
key: 'stageTime',
45+
sortable: false
46+
},
47+
{
48+
key: 'type',
49+
sortable: true
50+
},
51+
{
52+
key: 'command',
53+
sortable: false
54+
},
55+
{
56+
key: 'team',
57+
sortable: false
58+
},
59+
{
60+
key: 'description',
61+
sortable: false
62+
},
63+
],
64+
}
65+
},
66+
computed: {
67+
gameEvents() {
68+
return this.$store.state.gameEvents;
69+
}
70+
},
71+
methods: {
72+
formatTimestamp(timestamp) {
73+
let date = new Date(timestamp);
74+
return date.format("HH:MM:ss,l");
75+
}
76+
}
77+
}
78+
</script>
79+
80+
<style scoped>
81+
82+
</style>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<template>
2+
<div class="container">
3+
<span v-b-tooltip.hover
4+
v-for="event in events"
5+
:key="event.key"
6+
:title="event.title">
7+
<b-button v-on:click="send('stop')"
8+
v-bind:disabled="false">
9+
{{event.name}}
10+
</b-button>
11+
</span>
12+
</div>
13+
</template>
14+
15+
<script>
16+
export default {
17+
name: "ControlTeamEvent",
18+
props: {
19+
teamColor: String,
20+
events: {
21+
type: Array,
22+
default: function () {
23+
return [
24+
{key: 'BALL_LEFT_FIELD', name: 'BALL_LEFT_FIELD', title: ''},
25+
{key: 'GOAL', name: 'GOAL', title: ''},
26+
{key: 'KICK_TIMEOUT', name: 'KICK_TIMEOUT', title: ''},
27+
{key: 'NO_PROGRESS_IN_GAME', name: 'NO_PROGRESS_IN_GAME', title: ''},
28+
{key: 'BOT_COLLISION', name: 'BOT_COLLISION', title: ''},
29+
{key: 'MULTIPLE_DEFENDER', name: 'MULTIPLE_DEFENDER', title: ''},
30+
{key: 'MULTIPLE_DEFENDER_PARTIALLY', name: 'MULTIPLE_DEFENDER_PARTIALLY', title: ''},
31+
{key: 'ATTACKER_IN_DEFENSE_AREA', name: 'ATTACKER_IN_DEFENSE_AREA', title: ''},
32+
{key: 'ICING', name: 'ICING', title: ''},
33+
{key: 'BALL_SPEED', name: 'BALL_SPEED', title: ''},
34+
{key: 'ROBOT_STOP_SPEED', name: 'ROBOT_STOP_SPEED', title: ''},
35+
{key: 'BALL_DRIBBLING', name: 'BALL_DRIBBLING', title: ''},
36+
{key: 'ATTACKER_TOUCH_KEEPER', name: 'ATTACKER_TOUCH_KEEPER', title: ''},
37+
{key: 'DOUBLE_TOUCH', name: 'DOUBLE_TOUCH', title: ''},
38+
{key: 'ATTACKER_TO_DEFENCE_AREA', name: 'ATTACKER_TO_DEFENCE_AREA', title: ''},
39+
{key: 'DEFENDER_TO_KICK_POINT_DISTANCE', name: 'DEFENDER_TO_KICK_POINT_DISTANCE', title: ''},
40+
{key: 'BALL_HOLDING', name: 'BALL_HOLDING', title: ''},
41+
{key: 'INDIRECT_GOAL', name: 'INDIRECT_GOAL', title: ''},
42+
{key: 'BALL_PLACEMENT_FAILED', name: 'BALL_PLACEMENT_FAILED', title: ''},
43+
{key: 'CHIP_ON_GOAL', name: 'CHIP_ON_GOAL', title: ''},
44+
]
45+
}
46+
}
47+
},
48+
methods: {
49+
send: function (command) {
50+
this.$socket.sendObj({'command': {'commandType': command}})
51+
}
52+
},
53+
}
54+
</script>
55+
56+
<style scoped>
57+
button {
58+
margin: 0.1em;
59+
}
60+
61+
.container {
62+
display: flex;
63+
flex-direction: column;
64+
}
65+
</style>

0 commit comments

Comments
 (0)