Add standard AI type
This commit is contained in:
parent
e2d71b80f1
commit
2b2e45d7f2
16
flags.go
16
flags.go
|
@ -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))
|
||||
}
|
||||
|
|
44
game/game.go
44
game/game.go
|
@ -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 {
|
||||
|
|
110
game/inputs.go
110
game/inputs.go
|
@ -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 {
|
||||
|
|
1
main.go
1
main.go
|
@ -22,6 +22,7 @@ func main() {
|
|||
|
||||
ebiten.SetTPS(world.TPS)
|
||||
|
||||
ebiten.SetFullscreen(world.Fullscreen)
|
||||
if world.DisableVsync {
|
||||
ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMaximum)
|
||||
} else {
|
||||
|
|
11
system/ui.go
11
system/ui.go
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue