Skip to content

Commit 5ed69f7

Browse files
committed
Add initial config tournament
1 parent 817ee7a commit 5ed69f7

File tree

5 files changed

+116
-38
lines changed

5 files changed

+116
-38
lines changed

commands/test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,14 @@ func (d *TestBot) Run(args []string, streams cli.Streams) error {
5555
return err
5656
}
5757
timeout, cancelFunc := context.WithTimeout(context.Background(), d.Timeout)
58-
err = battle.Run(timeout)
58+
_ = battle.Run(timeout)
5959
cancelFunc()
6060
output := log.New(streams.Out, "", 0)
61+
return analizeTestBattle(output, streams, &battle)
62+
}
63+
64+
func analizeTestBattle(output *log.Logger, streams cli.Streams, battle *games.Battle) error {
65+
err := battle.Err()
6166
if err != nil {
6267
if conflict := new(games.ConflictScore); errors.As(err, &conflict) {
6368
output.Printf(`Либо бот упал с ошибкой, либо он некорректно возвращает результат игры.
@@ -66,15 +71,16 @@ Exit code должен быть: победа - 0, поражение - 3, ни
6671
`, conflict.Scores[0].String(), conflict.Scores[1].String())
6772
return err
6873
}
69-
if errors.Is(timeout.Err(), context.DeadlineExceeded) {
74+
if errors.Is(err, context.DeadlineExceeded) {
7075
output.Println("Бот слишком долго играл, возможно, завис")
71-
return timeout.Err()
76+
return err
7277
}
7378
_, _ = fmt.Fprintln(streams.Out, "Проблемы с ботом", err)
7479
return err
7580
}
7681
_, _ = fmt.Fprintf(streams.Out, `Тестирование прошло успешно, бот способен играть сам с собой.
82+
Длительность партии: %s.
7783
Результат ходившего первым: %s
78-
`, battle.GameResult(0))
84+
`, battle.Duration(), battle.GameResult(0))
7985
return nil
8086
}

commands/tournament.go

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@ package commands
22

33
import (
44
"errors"
5+
"github.com/RobolabGs2/botctl/cli"
6+
"github.com/RobolabGs2/botctl/executil"
7+
"github.com/RobolabGs2/botctl/games"
8+
"gopkg.in/yaml.v3"
59
"io/fs"
610
"log"
711
"os"
812
"path"
913
"runtime"
1014
"sync"
11-
12-
"github.com/RobolabGs2/botctl/cli"
13-
"github.com/RobolabGs2/botctl/executil"
14-
"github.com/RobolabGs2/botctl/games"
15-
"gopkg.in/yaml.v3"
15+
"time"
1616
)
1717

1818
type Tournament struct {
1919
Concurrency int `name:"c" default:"0" desc:"Количество одновременных соревнований. '0' - количество виртуальных ядер минус 1"`
20-
BotsList string `name:"config" default:"tournament.yaml" desc:"A ботов для турнира"`
20+
Config string `name:"config" default:"tournament.yaml" desc:"Список ботов для турнира"`
21+
config TournamentConfigs
2122
}
2223

2324
func (t *Tournament) Description() string {
@@ -52,6 +53,11 @@ func (t *Tournament) Run(args []string, streams cli.Streams) error {
5253
if err != nil {
5354
return err
5455
}
56+
testBots(bots, streams, t.Concurrency)
57+
return runTournament(bots, botsDesc, streams, t.Concurrency)
58+
}
59+
60+
func runTournament(bots []games.Bot, botsDesc []BotDescription, streams cli.Streams, concurrency int) error {
5561
battles := make(chan *games.Battle)
5662
go func() {
5763
for i, first := range bots {
@@ -65,15 +71,37 @@ func (t *Tournament) Run(args []string, streams cli.Streams) error {
6571

6672
scores := MakeScoreTable(botsDesc)
6773
output := log.New(streams.Out, "", 0)
68-
for battle := range RunRunners(t.Concurrency, battles) {
74+
for battle := range RunRunners(concurrency, battles) {
6975
err := scores.Update(battle)
7076
if err != nil {
71-
return err
77+
output.Printf(
78+
"Неудачное сражение между %s vs %s: %s",
79+
battle.Players[0].Name, battle.Players[1].Name, err)
80+
continue
7281
}
73-
log.Println(battle.State(),
82+
output.Println(scores)
83+
output.Println(battle.State(),
7484
battle.Players[0].Name, battle.GameResult(0),
7585
battle.Players[1].Name, battle.GameResult(1))
76-
output.Println(scores)
86+
output.Println()
87+
}
88+
return nil
89+
}
90+
91+
func testBots(bots []games.Bot, streams cli.Streams, concurrency int) error {
92+
battles := make(chan *games.Battle)
93+
go func() {
94+
for _, bot := range bots {
95+
battles <- &games.Battle{Players: [2]games.Bot{bot, bot}}
96+
}
97+
close(battles)
98+
}()
99+
100+
output := log.New(streams.Out, "", 0)
101+
for battle := range RunRunners(concurrency, battles) {
102+
output.Println("Результат тестирования бота", battle.Players[0].Name)
103+
_ = analizeTestBattle(output, streams, battle)
104+
output.Println()
77105
}
78106
return nil
79107
}
@@ -90,34 +118,47 @@ func MakeBots(botsDesc []BotDescription, dirName string) ([]games.Bot, error) {
90118
return bots, nil
91119
}
92120

121+
type BotsConfig map[string]BotDescription
122+
123+
func (bots BotsConfig) Slice() []BotDescription {
124+
botsDesc := make([]BotDescription, 0, len(bots))
125+
for author, bot := range bots {
126+
bot.Author = author
127+
botsDesc = append(botsDesc, bot)
128+
}
129+
return botsDesc
130+
}
131+
132+
type TournamentConfigs struct {
133+
Bots BotsConfig
134+
Timeout time.Duration
135+
Game string
136+
Rounds int
137+
}
138+
93139
func (t *Tournament) readBotDescriptions(dir fs.FS) ([]BotDescription, error) {
94-
var botsDesc []BotDescription
95-
if executil.CheckFileFs(dir, t.BotsList) == nil {
96-
config, err := dir.Open(t.BotsList)
140+
if executil.CheckFileFs(dir, t.Config) == nil {
141+
config, err := dir.Open(t.Config)
97142
if err != nil {
98143
return nil, err
99144
}
100-
bots := map[string]BotDescription{}
101-
if err := yaml.NewDecoder(config).Decode(bots); err != nil {
102-
return nil, err
103-
}
104-
for author, bot := range bots {
105-
bot.Author = author
106-
botsDesc = append(botsDesc, bot)
107-
}
108-
} else {
109-
files, err := fs.ReadDir(dir, ".")
110-
if err != nil {
145+
if err := yaml.NewDecoder(config).Decode(&t.config); err != nil {
111146
return nil, err
112147
}
113-
for _, file := range files {
114-
if executil.Executable(file) {
115-
log.Println("Detect bot", file.Name())
116-
botsDesc = append(botsDesc, BotDescription{
117-
Author: file.Name(),
118-
Cmd: file.Name(),
119-
})
120-
}
148+
return t.config.Bots.Slice(), nil
149+
}
150+
var botsDesc []BotDescription
151+
files, err := fs.ReadDir(dir, ".")
152+
if err != nil {
153+
return nil, err
154+
}
155+
for _, file := range files {
156+
if executil.Executable(file) {
157+
log.Println("Detect bot", file.Name())
158+
botsDesc = append(botsDesc, BotDescription{
159+
Author: file.Name(),
160+
Cmd: file.Name(),
161+
})
121162
}
122163
}
123164
return botsDesc, nil
@@ -133,7 +174,7 @@ func RunRunners(concurrency int, battles chan *games.Battle) chan *games.Battle
133174
runnersGroup.Add(concurrency)
134175
finished := make(chan *games.Battle)
135176
for i := 0; i < concurrency; i++ {
136-
go games.Runner(runnersGroup, battles, finished)
177+
go games.RunnerWithTimeout(2*time.Minute, runnersGroup, battles, finished)
137178
}
138179
go func() {
139180
runnersGroup.Wait()

executil/exec_util_windows.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
//go:build windows
12
// +build windows
23

34
package executil
45

56
import (
7+
"errors"
68
"fmt"
79
"io/fs"
810
"os"
911
"os/exec"
1012
"strconv"
1113
"strings"
14+
"syscall"
1215
)
1316

1417
func MakeCmd(args ...string) *exec.Cmd {
@@ -17,6 +20,9 @@ func MakeCmd(args ...string) *exec.Cmd {
1720

1821
func CheckFile(filename string) error {
1922
if _, err := os.Stat(filename); err != nil {
23+
if errors.Is(err, syscall.ERROR_FILE_NOT_FOUND) {
24+
return fmt.Errorf("файл %q не найден", filename)
25+
}
2026
return err
2127
}
2228
return nil

games/battle.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"sync"
10+
"time"
1011
)
1112

1213
type BattleState int
@@ -31,7 +32,13 @@ type Battle struct {
3132
scores [2]GameResult
3233
logs [2]Buffer
3334

34-
Players [2]Bot
35+
Players [2]Bot
36+
StartedAt time.Time
37+
FinishedAt time.Time
38+
}
39+
40+
func (b *Battle) Duration() time.Duration {
41+
return b.FinishedAt.Sub(b.StartedAt)
3542
}
3643

3744
func (battle *Battle) Logs(player int) string {
@@ -91,7 +98,13 @@ func (b *Buffer) ReadAll() string {
9198
func (battle *Battle) Run(ctx context.Context) (err error) {
9299
battle.stateMut.Lock()
93100
battle.state = BattleRunning
101+
battle.StartedAt = time.Now()
94102
battle.stateMut.Unlock()
103+
defer func() {
104+
battle.stateMut.Lock()
105+
battle.FinishedAt = time.Now()
106+
battle.stateMut.Unlock()
107+
}()
95108
defer func() {
96109
if err != nil {
97110
battle.stateMut.Lock()

games/runner.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package games
33
import (
44
"context"
55
"sync"
6+
"time"
67
)
78

89
func Runner(group *sync.WaitGroup, battles <-chan *Battle, finished chan<- *Battle) {
@@ -13,3 +14,14 @@ func Runner(group *sync.WaitGroup, battles <-chan *Battle, finished chan<- *Batt
1314
finished <- battle
1415
}
1516
}
17+
18+
func RunnerWithTimeout(timeout time.Duration, group *sync.WaitGroup, battles <-chan *Battle, finished chan<- *Battle) {
19+
defer group.Done()
20+
for battle := range battles {
21+
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
22+
// battle contains error, this error should be handled by a receiver of finished chan
23+
_ = battle.Run(ctx)
24+
cancel()
25+
finished <- battle
26+
}
27+
}

0 commit comments

Comments
 (0)