You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
5.3 KiB
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
|
|
}
|