-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
321b0a0
commit 104880c
Showing
51 changed files
with
4,590 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"math/rand" | ||
"os" | ||
"os/signal" | ||
"time" | ||
|
||
"github.com/sirupsen/logrus" | ||
|
||
"github.com/ivan1993spb/snake-bot/internal/config" | ||
"github.com/ivan1993spb/snake-bot/internal/connect" | ||
"github.com/ivan1993spb/snake-bot/internal/core" | ||
"github.com/ivan1993spb/snake-bot/internal/secure" | ||
"github.com/ivan1993spb/snake-bot/internal/server" | ||
"github.com/ivan1993spb/snake-bot/internal/utils" | ||
) | ||
|
||
const ApplicationName = "Snake-Bot" | ||
|
||
var ( | ||
Version = "dev" | ||
Build = "dev" | ||
) | ||
|
||
func init() { | ||
rand.Seed(time.Now().UnixNano()) | ||
} | ||
|
||
func main() { | ||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) | ||
defer cancel() | ||
|
||
cfg, err := config.StdConfig() | ||
|
||
{ | ||
logger := utils.NewLogger(cfg.Log) | ||
ctx = utils.LogContext(ctx, logger) | ||
} | ||
|
||
if err != nil { | ||
utils.Log(ctx).WithError(err).Fatal("config fail") | ||
} | ||
|
||
utils.Log(ctx).WithFields(logrus.Fields{ | ||
"version": Version, | ||
"build": Build, | ||
}).Info("Welcome to Snake-Bot!") | ||
|
||
sec := secure.NewSecure() | ||
if err := sec.GenerateToken(os.Stdout); err != nil { | ||
utils.Log(ctx).WithError(err).Fatal("security fail") | ||
} | ||
utils.Log(ctx).Warn("auth token successfully generated") | ||
|
||
headerAppInfo := utils.FormatAppInfoHeader(ApplicationName, Version, Build) | ||
|
||
connector := connect.NewConnector(cfg.Target, headerAppInfo) | ||
|
||
c := core.NewCore(ctx, connector, cfg.Bots.Limit) | ||
|
||
serv := server.NewServer(ctx, cfg.Server, headerAppInfo, c, sec) | ||
|
||
if err := serv.ListenAndServe(ctx); err != nil { | ||
utils.Log(ctx).WithError(err).Fatal("server error") | ||
} | ||
|
||
utils.Log(ctx).Info("buh bye!") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package snakebot | ||
|
||
import _ "embed" | ||
|
||
//go:embed api/openapi.yaml | ||
var OpenAPISpec string |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module github.com/ivan1993spb/snake-bot | ||
|
||
go 1.16 | ||
|
||
require ( | ||
github.com/go-chi/chi/v5 v5.0.5 | ||
github.com/go-chi/cors v1.2.0 | ||
github.com/gorilla/websocket v1.4.2 | ||
github.com/kr/text v0.2.0 // indirect | ||
github.com/pkg/errors v0.9.1 | ||
github.com/prometheus/client_golang v1.11.0 | ||
github.com/sirupsen/logrus v1.8.1 | ||
github.com/stretchr/testify v1.7.0 | ||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect | ||
gopkg.in/yaml.v2 v2.4.0 | ||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect | ||
lukechampine.com/noescape v0.0.0-20191006153127-214c369a3d1b | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package bot | ||
|
||
import ( | ||
"context" | ||
"math/rand" | ||
"sync/atomic" | ||
"time" | ||
|
||
"github.com/sirupsen/logrus" | ||
|
||
"github.com/ivan1993spb/snake-bot/internal/bot/engine" | ||
"github.com/ivan1993spb/snake-bot/internal/types" | ||
"github.com/ivan1993spb/snake-bot/internal/utils" | ||
) | ||
|
||
const botTickTime = time.Millisecond * 200 | ||
|
||
const lookupDistance uint8 = 50 | ||
|
||
const directionExpireTime = time.Millisecond * 10 | ||
|
||
const ( | ||
stateWait = iota | ||
stateExplore | ||
) | ||
|
||
type World interface { | ||
LookAround(sight engine.Sight) *engine.HashmapSight | ||
GetArea() engine.Area | ||
GetObject(id uint32) (*types.Object, bool) | ||
} | ||
|
||
type Bot struct { | ||
state uint32 | ||
|
||
myId uint32 | ||
world World | ||
|
||
discoverer engine.Discoverer | ||
|
||
lastPosition types.Dot | ||
lastDirection types.Direction | ||
} | ||
|
||
func NewBot(world World, discoverer engine.Discoverer) *Bot { | ||
return &Bot{ | ||
world: world, | ||
discoverer: discoverer, | ||
} | ||
} | ||
|
||
func NewDijkstrasBot(world World) *Bot { | ||
r := rand.New(rand.NewSource(time.Now().UnixNano())) | ||
discoverer := engine.NewDijkstrasDiscoverer(r) | ||
cacher := engine.NewCacherDiscoverer(discoverer) | ||
return NewBot(world, cacher) | ||
} | ||
|
||
func (b *Bot) Run(ctx context.Context, stop <-chan struct{}) <-chan types.Direction { | ||
chout := make(chan types.Direction) | ||
|
||
go func() { | ||
defer close(chout) | ||
ticker := time.NewTicker(botTickTime) | ||
defer ticker.Stop() | ||
|
||
for { | ||
select { | ||
case <-ticker.C: | ||
direction, ok := b.operate(ctx) | ||
if ok { | ||
ctx, cancel := context.WithTimeout(ctx, directionExpireTime) | ||
select { | ||
case chout <- direction: | ||
case <-ctx.Done(): | ||
} | ||
cancel() | ||
} | ||
case <-stop: | ||
return | ||
case <-ctx.Done(): | ||
return | ||
} | ||
} | ||
}() | ||
|
||
return chout | ||
} | ||
|
||
func (b *Bot) getState() uint32 { | ||
return atomic.LoadUint32(&b.state) | ||
} | ||
|
||
func (b *Bot) getMe() uint32 { | ||
return atomic.LoadUint32(&b.myId) | ||
} | ||
|
||
func (b *Bot) operate(ctx context.Context) (types.Direction, bool) { | ||
if b.getState() != stateExplore { | ||
return types.DirectionZero, false | ||
} | ||
|
||
me, ok := b.world.GetObject(b.getMe()) | ||
if !ok { | ||
return types.DirectionZero, false | ||
} | ||
|
||
area := b.world.GetArea() | ||
objectDots := me.GetDots() | ||
if len(objectDots) == 0 { | ||
return types.DirectionZero, false | ||
} | ||
head := objectDots[0] | ||
sight := engine.NewSight(area, head, lookupDistance) | ||
|
||
objects := b.world.LookAround(sight) | ||
scores := b.score(objects) | ||
|
||
path := b.discoverer.Discover(head, area, sight, scores) | ||
if len(path) == 0 { | ||
return types.DirectionZero, false | ||
} | ||
direction := area.FindDirection(head, path[0]) | ||
|
||
if area.FindDirection(objectDots[1], objectDots[0]) == direction { | ||
// the same direction | ||
return types.DirectionZero, false | ||
} | ||
if b.lastDirection != direction || b.lastPosition != head { | ||
utils.Log(ctx).WithFields(logrus.Fields{ | ||
"head": head, | ||
"direction": direction, | ||
}).Debugln("change direction") | ||
b.lastDirection = direction | ||
b.lastPosition = head | ||
|
||
return direction, true | ||
} | ||
return types.DirectionZero, false | ||
} | ||
|
||
func (b *Bot) Countdown(sec int) { | ||
// TODO: Shut down the snake if the stateWait | ||
atomic.StoreUint32(&b.state, stateWait) | ||
} | ||
|
||
func (b *Bot) Me(id uint32) { | ||
atomic.StoreUint32(&b.myId, id) | ||
atomic.StoreUint32(&b.state, stateExplore) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package bot | ||
|
||
import ( | ||
"github.com/ivan1993spb/snake-bot/internal/bot/engine" | ||
"github.com/ivan1993spb/snake-bot/internal/types" | ||
) | ||
|
||
type scoreType uint8 | ||
|
||
const ( | ||
scoreTypeCollapse scoreType = iota | ||
scoreTypeFoodApple | ||
scoreTypeFoodCorpse | ||
scoreTypeFoodWatermelon | ||
scoreTypeFoodMouse | ||
scoreTypeHunt | ||
) | ||
|
||
var defaultBehavior = map[scoreType]int{ | ||
scoreTypeCollapse: -1000, | ||
scoreTypeFoodApple: 1, | ||
scoreTypeFoodCorpse: 2, | ||
scoreTypeFoodWatermelon: 5, | ||
scoreTypeFoodMouse: 15, | ||
scoreTypeHunt: 30, | ||
} | ||
|
||
func (b *Bot) score(objects *engine.HashmapSight) *engine.HashmapSight { | ||
// TODO: Estimate the real prey length. | ||
const preyLen = 3 | ||
|
||
scores := objects.Reflect() | ||
|
||
objects.ForEach(func(dot types.Dot, v interface{}) { | ||
object, ok := v.(*types.Object) | ||
if !ok { | ||
return | ||
} | ||
if object.Id == b.myId { | ||
scores.Assign(dot, defaultBehavior[scoreTypeCollapse]) | ||
} else if object.Type == types.ObjectTypeSnake { | ||
if len(object.Dots) == preyLen { | ||
scores.Assign(dot, defaultBehavior[scoreTypeHunt]) | ||
} else { | ||
scores.Assign(dot, defaultBehavior[scoreTypeCollapse]) | ||
} | ||
} else if object.Type == types.ObjectTypeWall { | ||
scores.Assign(dot, defaultBehavior[scoreTypeCollapse]) | ||
} else { | ||
switch object.Type { | ||
case types.ObjectTypeApple: | ||
scores.Assign(dot, defaultBehavior[scoreTypeFoodApple]) | ||
case types.ObjectTypeCorpse: | ||
scores.Assign(dot, defaultBehavior[scoreTypeFoodCorpse]) | ||
case types.ObjectTypeWatermelon: | ||
scores.Assign(dot, defaultBehavior[scoreTypeFoodWatermelon]) | ||
case types.ObjectTypeMouse: | ||
scores.Assign(dot, defaultBehavior[scoreTypeFoodMouse]) | ||
default: | ||
// Avoid unknown objects. | ||
scores.Assign(dot, defaultBehavior[scoreTypeCollapse]) | ||
} | ||
} | ||
}) | ||
|
||
return scores | ||
} |
Oops, something went wrong.