Add standard AI type

This commit is contained in:
Trevor Slocum 2023-01-28 18:16:19 -08:00
parent e2d71b80f1
commit 2b2e45d7f2
6 changed files with 161 additions and 25 deletions

View File

@ -10,29 +10,23 @@ import (
"code.rocketnine.space/tslocum/boxbrawl/world"
"github.com/assemblaj/ggpo"
"github.com/hajimehoshi/ebiten/v2"
)
func parseFlags() {
var (
fullscreen bool
hostAddress string
connectAddress string
printDebug bool
)
flag.BoolVar(&fullscreen, "fullscreen", false, "run in fullscreen mode")
flag.BoolVar(&world.DisableVsync, "no-vsync", false, "turn off vsync and run at maximum fps")
flag.StringVar(&hostAddress, "host", "", "start hosting a match on specified address:port")
flag.StringVar(&connectAddress, "connect", "", "connect to a match at specified address:port")
flag.IntVar(&world.LocalPort, "local", 0, "set local port (this is not normally required)")
flag.BoolVar(&world.Fullscreen, "fullscreen", false, "run in fullscreen mode")
flag.BoolVar(&world.DisableVsync, "no-vsync", false, "do not enable vsync (allows the game to run at maximum fps)")
flag.StringVar(&hostAddress, "host", "", "start hosting a match against a remote opponent at the specified address:port")
flag.StringVar(&connectAddress, "connect", "", "connect to a match hosted by a remote opponent at the specified address:port")
flag.IntVar(&world.LocalPort, "local", 0, "set local port (this should be different from your opponent's local port)")
flag.BoolVar(&printDebug, "debug", false, "enable printing debug messages")
flag.IntVar(&world.TPS, "tps", world.DefaultTPS, "set ticks per second (this is not normally required)")
flag.Parse()
if fullscreen {
ebiten.SetFullscreen(true)
}
if printDebug {
ggpo.SetLogger(log.New(os.Stderr, "GGPO ", log.Ldate|log.Ltime|log.Lmsgprefix))
}

View File

@ -79,6 +79,9 @@ func (g *Game) reset() {
}
g.Winner = 0
botTauntTicks = 0
botTauntTotalTicks = 0
}
func (g *Game) Layout(_, _ int) (screenWidth, screenHeight int) {
@ -214,7 +217,7 @@ func (g *Game) ReadInputs() InputBits {
in.setButtonOn(ButtonTaunt)
}
if ebiten.IsKeyPressed(ebiten.KeyEnter) {
if ebiten.IsKeyPressed(ebiten.KeyEnter) || ebiten.IsKeyPressed(ebiten.KeyKPEnter) {
in.setButtonOn(ButtonStart)
}
@ -261,7 +264,11 @@ func (g *Game) applyPhysics() {
}
}
if collideG == -1 && p.VY > -world.Gravity {
outOfBounds := p.Y < -world.GroundHeight-component.PlayerHeight
if outOfBounds {
p.VX, p.VY = 0, 0
} else if collideG == -1 && p.VY > -world.Gravity {
p.VY -= math.Max(math.Abs(p.VY/2.5), 0.1)
if p.VY < -world.Gravity {
p.VY = -world.Gravity
@ -313,13 +320,15 @@ func (g *Game) applyPhysics() {
p.VX, o.VX = o.VX, p.VX // TODO
}
if g.Winner == 0 && p.Y < -world.GroundHeight {
if g.Winner == 0 && outOfBounds {
g.Winner = o.PlayerNum
}
}
}
func (g *Game) UpdateByInputs(inputs []InputBits) {
const punchStunTicks = 15
var player, opponent *component.Player
var playerFlipped, oppFlipped bool
for i, input := range inputs {
@ -443,7 +452,7 @@ func (g *Game) UpdateByInputs(inputs []InputBits) {
// Stun the opponent.
if g.Players[opp].Action != component.ActionBlock {
opponent.Action = component.ActionStunned
opponent.ActionTicksLeft = 14
opponent.ActionTicksLeft = punchStunTicks
}
}
}
@ -473,7 +482,11 @@ func (g *Game) UpdateByInputs(inputs []InputBits) {
}
if world.Winner != 0 && input.isButtonOn(ButtonStart) {
g.Players[i].PlayAgain = true
if world.Local {
g.reset()
} else {
g.Players[i].PlayAgain = true
}
}
}
@ -501,7 +514,9 @@ func (g *Game) RunLocalFrame() {
inputs := make([]InputBits, 2)
inputs[0] = g.ReadInputs()
if world.AI == world.AIMirror {
if world.AI == world.AIStandard {
inputs[1] = botInput()
} else if world.AI == world.AIMirror {
inputs[1] = mirrorInput(inputs[0])
} else if world.AI == world.AIBlock {
inputs[1] = inputs[0]
@ -568,17 +583,28 @@ func (g *Game) Update() error {
return nil
}
if inpututil.IsKeyJustPressed(ebiten.KeyI) && ebiten.IsKeyPressed(ebiten.KeyControl) {
// Toggle fullscreen.
if (inpututil.IsKeyJustPressed(ebiten.KeyEnter) || inpututil.IsKeyJustPressed(ebiten.KeyKPEnter)) && ebiten.IsKeyPressed(ebiten.KeyAlt) {
world.Fullscreen = !world.Fullscreen
ebiten.SetFullscreen(world.Fullscreen)
return nil
}
// Change local opponent type.
if inpututil.IsKeyJustPressed(ebiten.KeyO) && ebiten.IsKeyPressed(ebiten.KeyControl) {
switch world.AI {
case world.AIStandard:
world.AI = world.AIMirror
case world.AIMirror:
world.AI = world.AIBlock
case world.AIBlock:
world.AI = world.AINone
default:
world.AI = world.AIMirror
world.AI = world.AIStandard
}
}
// Change debug level.
if inpututil.IsKeyJustPressed(ebiten.KeyV) && ebiten.IsKeyPressed(ebiten.KeyControl) {
world.Debug++
if world.Debug > world.MaxDebug {
@ -586,6 +612,7 @@ func (g *Game) Update() error {
}
}
// Decreate TPS.
if inpututil.IsKeyJustPressed(ebiten.KeyMinus) && ebiten.IsKeyPressed(ebiten.KeyControl) {
for i, preset := range world.TPSPresets {
if preset == world.TPS {
@ -599,6 +626,7 @@ func (g *Game) Update() error {
}
}
// Increase TPS.
if inpututil.IsKeyJustPressed(ebiten.KeyEqual) && ebiten.IsKeyPressed(ebiten.KeyControl) {
for i, preset := range world.TPSPresets {
if preset == world.TPS {

View File

@ -5,6 +5,11 @@ import (
"encoding/gob"
"fmt"
"log"
"math"
"math/rand"
"code.rocketnine.space/tslocum/boxbrawl/component"
"code.rocketnine.space/tslocum/boxbrawl/world"
)
type Input struct {
@ -105,6 +110,111 @@ func encodeInputsGob(inputs Input) []byte {
return buf.Bytes()
}
var (
botTicks int
botWait bool
botLastPunch int
botBlockTicks int
botTaunt component.PlayerAction
botTauntTicks int
botTauntTotalTicks int
)
// Calculate AI opponent input.
func botInput() InputBits {
var input InputBits
p := &world.Player2
o := &world.Player1
const (
botMaxActionTime = 20
botWaitChance = 3
botPunchDistance = 25
botBlockDistance = 25
botBlockTime = 17
botBlockChance = 4
botTauntMinTime = 60
botTauntTime = 200
botTauntMaxTime = 550
)
if botTicks == 0 {
botTicks = rand.Intn(botMaxActionTime)
botWait = rand.Intn(botWaitChance) == 0 && world.Winner == 0
} else {
botTicks--
}
if !botWait {
if (botTauntTicks > 0 || world.Winner == 2) && botTauntTotalTicks < botTauntMaxTime {
if botTauntTicks == 0 {
if p.Action == component.ActionIdle {
for {
var newTaunt component.PlayerAction
botTauntInt := rand.Intn(4)
switch botTauntInt {
case 0:
newTaunt = component.ActionTaunt1
case 1:
newTaunt = component.ActionTaunt2
case 2:
newTaunt = component.ActionTaunt3
case 3:
newTaunt = component.ActionTaunt4
}
if newTaunt != botTaunt {
botTaunt = newTaunt
break
}
}
botTauntTicks = botTauntMinTime + rand.Intn(botTauntTime)
botTauntTotalTicks++
}
} else {
input.setButtonOn(ButtonTaunt)
switch botTaunt {
case component.ActionTaunt1:
input.setButtonOn(ButtonUp)
case component.ActionTaunt2:
input.setButtonOn(ButtonRight)
case component.ActionTaunt3:
input.setButtonOn(ButtonDown)
case component.ActionTaunt4:
input.setButtonOn(ButtonLeft)
}
botTauntTicks--
botTauntTotalTicks++
}
return input
}
if world.Winner == 0 {
if p.X < o.X {
input.setButtonOn(ButtonRight)
} else {
input.setButtonOn(ButtonLeft)
}
}
if botBlockTicks > 0 || (math.Abs(p.X-o.X) < botBlockDistance && o.Action == component.ActionPunch && rand.Intn(botBlockChance) != 0) {
input.setButtonOn(ButtonBlock)
if botBlockTicks > 0 {
botBlockTicks--
} else {
botBlockTicks = botBlockTime
}
} else if math.Abs(p.X-o.X) < botPunchDistance && botLastPunch > 1 {
input.setButtonOn(ButtonPunch)
botLastPunch = 0
}
}
if !input.isButtonOn(ButtonPunch) {
botLastPunch++
}
return input
}
func mirrorInput(inputs InputBits) InputBits {
left, right := inputs.isButtonOn(ButtonRight), inputs.isButtonOn(ButtonLeft)
if left {

View File

@ -22,6 +22,7 @@ func main() {
ebiten.SetTPS(world.TPS)
ebiten.SetFullscreen(world.Fullscreen)
if world.DisableVsync {
ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMaximum)
} else {

View File

@ -255,21 +255,22 @@ func (u *UISystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
aiLabel := "NONE"
switch world.AI {
case world.AIStandard:
aiLabel = "STANDARD"
case world.AIMirror:
aiLabel = "MIRROR"
case world.AIBlock:
aiLabel = "BLOCK"
}
label := fmt.Sprintf("AI TYPE: %s (CONTROL+I)", aiLabel)
label := fmt.Sprintf("OPPONENT TYPE: %s (CONTROL+O)", aiLabel)
ebitenutil.DebugPrint(u.tmpImg, label)
width := float64(len(label) * 12)
height := float64(32)
width := float64(len(label) * 6)
op := &ebiten.DrawImageOptions{}
op.GeoM.Reset()
op.GeoM.Scale(2, 2)
op.GeoM.Translate(world.InternalScreenWidth/2-width/2, world.InternalScreenHeight-height)
op.GeoM.Scale(1, 1)
op.GeoM.Translate(world.InternalScreenWidth/2-width/2, 0)
screen.DrawImage(u.tmpImg, op)
}

View File

@ -30,14 +30,16 @@ const (
type AIType int
const (
AINone AIType = iota
AIStandard AIType = iota
AIMirror
AIBlock
AINone
)
var (
TPS = DefaultTPS
Fullscreen bool
DisableVsync bool
ScreenWidth, ScreenHeight = 0, 0