boxbrawl/game/inputs.go

246 lines
5.3 KiB
Go

package game
import (
"bytes"
"encoding/gob"
"fmt"
"log"
"math"
"math/rand"
"code.rocketnine.space/tslocum/boxbrawl/component"
"code.rocketnine.space/tslocum/boxbrawl/world"
)
type Input struct {
ButtonMap int
}
type InputBits int
type InputButton int
const (
ButtonLeft InputButton = iota + 1
ButtonRight
ButtonDown
ButtonUp
ButtonPunch
ButtonKick
ButtonBlock
ButtonTaunt
ButtonStart
)
func (i *InputBits) isButtonOn(button InputButton) bool {
return *i&(1<<button) > 0
}
func (i *InputBits) setButtonOn(button InputButton) {
*i |= 1 << button
}
func (i *InputBits) setButtonOff(button InputButton) {
*i &^= 1 << button
}
func readI32(b []byte) int32 {
if len(b) < 4 {
return 0
}
return int32(b[0]) | int32(b[1])<<8 | int32(b[2])<<16 | int32(b[3])<<24
}
func writeI32(i32 int32) []byte {
b := []byte{byte(i32), byte(i32 >> 8), byte(i32 >> 16), byte(i32 >> 24)}
return b
}
func (i *Input) isButtonOn(button InputButton) bool {
return i.ButtonMap&(1<<button) > 0
}
func (i *Input) setButton(button InputButton) {
i.ButtonMap |= 1 << button
}
func (i Input) String() string {
return fmt.Sprintf("Input %d", i.ButtonMap)
}
func NewInput() Input {
return Input{}
}
func encodeInputs(inputs InputBits) []byte {
return writeI32(int32(inputs))
}
func decodeInputs(buffer [][]byte) []InputBits {
var inputs = make([]InputBits, len(buffer))
for i, b := range buffer {
inputs[i] = InputBits(readI32(b))
}
return inputs
}
func decodeInputsGob(buffer [][]byte) []Input {
var inputs = make([]Input, len(buffer))
for i, b := range buffer {
var buf bytes.Buffer = *bytes.NewBuffer(b)
dec := gob.NewDecoder(&buf)
err := dec.Decode(&inputs[i])
if err != nil {
log.Printf("decode error: %s. Returning empty input\n", err)
inputs[i] = NewInput()
} else {
log.Printf("inputs properly decoded: %s\n", inputs[i])
}
}
return inputs
}
func encodeInputsGob(inputs Input) []byte {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(&inputs)
if err != nil {
log.Fatal("encode error ", err)
}
return buf.Bytes()
}
var (
botTicks int
botWait bool
botLastPunch int
botLastKick 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 = 15
botWaitChance = 7
botPunchDistance = 25
botKickDistance = 25
botKickChance = 4
botBlockDistance = 35
botBlockTime = 20
botBlockChance = 2
botTauntMinTime = 60
botTauntTime = 200
botTauntMaxTime = 550
)
defer func() {
if !input.isButtonOn(ButtonPunch) {
botLastPunch++
}
if !input.isButtonOn(ButtonKick) {
botLastKick++
}
}()
if botTicks == 0 {
botTicks = rand.Intn(botMaxActionTime)
botWait = rand.Intn(botWaitChance) == 0 && world.Winner == 0
} else {
botTicks--
}
if !botWait {
// Handle taunting.
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 && p.X < world.GroundWidth/2-component.PlayerWidth {
input.setButtonOn(ButtonRight)
} else if p.X >= o.X && p.X > -world.GroundWidth/2 {
input.setButtonOn(ButtonLeft)
}
opponentAttacking := o.Action == component.ActionPunch || o.Action == component.ActionKick
if botBlockTicks > 0 || (math.Abs(p.X-o.X) < botBlockDistance && opponentAttacking && rand.Intn(botBlockChance) == 0) {
input.setButtonOn(ButtonBlock)
if botBlockTicks > 0 {
botBlockTicks--
} else {
botBlockTicks = botBlockTime
}
} else if p.Action == component.ActionIdle && math.Abs(p.X-o.X) < botKickDistance && rand.Intn(botKickChance) == 0 && botLastKick > 1 {
input.setButtonOn(ButtonKick)
botLastKick = 0
} else if p.Action == component.ActionIdle && math.Abs(p.X-o.X) < botPunchDistance && botLastPunch > 1 {
input.setButtonOn(ButtonPunch)
botLastPunch = 0
}
}
}
return input
}
func mirrorInput(inputs InputBits) InputBits {
left, right := inputs.isButtonOn(ButtonRight), inputs.isButtonOn(ButtonLeft)
if left {
inputs.setButtonOn(ButtonLeft)
} else {
inputs.setButtonOff(ButtonLeft)
}
if right {
inputs.setButtonOn(ButtonRight)
} else {
inputs.setButtonOff(ButtonRight)
}
return inputs
}