Add God mode

This commit is contained in:
Trevor Slocum 2024-02-02 21:13:32 -08:00
parent e6213d7863
commit 846ef25bdb
5 changed files with 123 additions and 52 deletions

17
flags.go Normal file
View file

@ -0,0 +1,17 @@
//go:build !js || !wasm
package main
import (
"flag"
"code.rocket9labs.com/tslocum/arcticwarfare/game"
)
func parseFlags() *game.Game {
var godMode bool
flag.BoolVar(&godMode, "god", false, "God mode")
flag.Parse()
return game.NewGame(godMode)
}

11
flags_web.go Normal file
View file

@ -0,0 +1,11 @@
//go:build js && wasm
package main
import (
"code.rocket9labs.com/tslocum/arcticwarfare/game"
)
func parseFlags() *game.Game {
return game.NewGame(false)
}

View file

@ -50,10 +50,12 @@ var tankNames = []string{
}
const (
SnowSize = 1024
tankSize = 100
tankOffset = 25
roundOffset = 30
SnowSize = 1024
tankSize = 100
tankOffset = 25
roundOffset = 30
cooldownWidth = 30
cooldownHeight = 5
)
var (
@ -62,6 +64,8 @@ var (
TreadMarkImage = ebiten.NewImage(3, 3)
RoundImage = ebiten.NewImage(4, 4)
CooldownImage = ebiten.NewImage(cooldownWidth, cooldownHeight)
)
var (
@ -78,6 +82,7 @@ func init() {
TreadMarkImage.Fill(color.RGBA{0, 0, 0, 7})
RoundImage.Fill(color.RGBA{0, 0, 0, 255})
CooldownImage.Fill(color.RGBA{0, 0, 255, 255})
}
type Game struct {
@ -86,7 +91,7 @@ type Game struct {
computerPlayers []int
}
func NewGame() *Game {
func NewGame(godMode bool) *Game {
ebiten.SetVsyncEnabled(true)
ebiten.SetScreenClearedEveryFrame(true)
ebiten.SetTPS(100)
@ -96,7 +101,7 @@ func NewGame() *Game {
ebiten.SetWindowTitle("Arctic Warfare")
g := &Game{
sim: NewSimulation(),
sim: NewSimulation(godMode),
}
const numComputerPlayers = 7
for i := 0; i < numComputerPlayers+1; i++ {
@ -113,7 +118,7 @@ const rad = math.Pi * 2
func (g *Game) setComputerInputs() {
const targetDistance = 150
const fireDistance = 750
states, _ := g.sim.State()
states, _, _ := g.sim.State()
nearestTarget := func(player int, x float64, y float64) (float64, float64) {
var nearestX, nearestY = -1.0, -1.0
var nearestDistance float64
@ -226,7 +231,7 @@ func (g *Game) Update() error {
g.sim.Tick()
playerStates, _ := g.sim.State()
playerStates, _, _ := g.sim.State()
const size = 11
for _, state := range playerStates {
@ -262,7 +267,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
screen.DrawImage(TreadMarkImage, op)
}
playerStates, roundStates := g.sim.State()
playerStates, roundStates, lastFire := g.sim.State()
{
const size = 4
@ -277,41 +282,75 @@ func (g *Game) Draw(screen *ebiten.Image) {
}
}
for i, state := range playerStates {
if i == 0 {
viewX, viewY = state.X-screenWidth/2+50, state.Y-screenHeight/2+50
if viewX < 0 {
viewX = 0
} else if viewX > worldWidth*worldTileSize-screenWidth {
viewX = worldWidth*worldTileSize - screenWidth
for j := 0; j < 2; j++ {
for i, state := range playerStates {
if (j == 0 && !state.Hit) || (j == 1 && state.Hit) {
continue
}
if viewY < 0 {
viewY = 0
} else if viewY > worldHeight*worldTileSize-screenHeight {
viewY = worldHeight*worldTileSize - screenHeight
}
}
if i == 0 {
viewX, viewY = state.X-screenWidth/2+50, state.Y-screenHeight/2+50
if viewX < 0 {
viewX = 0
} else if viewX > worldWidth*worldTileSize-screenWidth {
viewX = worldWidth*worldTileSize - screenWidth
}
if viewY < 0 {
viewY = 0
} else if viewY > worldHeight*worldTileSize-screenHeight {
viewY = worldHeight*worldTileSize - screenHeight
}
}
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-51, -51)
op.GeoM.Rotate(math.Pi/2 + state.R)
op.GeoM.Translate(51, 51)
op.GeoM.Translate(float64(state.X), float64(state.Y))
if state.Hit {
op.ColorScale.ScaleWithColor(color.RGBA{128, 128, 128, 255})
}
offset(op)
screen.DrawImage(tankHullImages[state.S], op)
op = &ebiten.DrawImageOptions{}
op.GeoM.Translate(-51, -51)
op.GeoM.Rotate(math.Pi/2 + state.R + state.T)
op.GeoM.Translate(51, 51)
op.GeoM.Translate(float64(state.X), float64(state.Y))
offset(op)
if state.Hit {
op.ColorScale.ScaleWithColor(color.RGBA{128, 128, 128, 255})
}
screen.DrawImage(tankTurretImages[state.S], op)
}
}
for i, state := range playerStates {
if state.Hit {
continue
}
last := lastFire[i]
delta := g.sim.offset + g.sim.frame - last
if last == 0 || delta >= fireCooldown {
continue
}
CooldownImage.Fill(color.RGBA{0, 0, 0, 0})
pct := float64(delta) / float64(fireCooldown)
w := float64(cooldownWidth) * pct
if w < 2 {
w = 2
}
fillColor := color.RGBA{100, 0, 0, 255}
if pct >= 0.66 {
fillColor = color.RGBA{200, 128, 0, 255}
} else if pct >= 0.33 {
fillColor = color.RGBA{150, 60, 0, 255}
}
CooldownImage.SubImage(image.Rect(0, 0, int(w), cooldownHeight)).(*ebiten.Image).Fill(fillColor)
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(-51, -51)
op.GeoM.Rotate(math.Pi/2 + state.R)
op.GeoM.Translate(51, 51)
op.GeoM.Translate(float64(state.X), float64(state.Y))
if state.Hit {
op.ColorScale.ScaleWithColor(color.RGBA{128, 128, 128, 255})
}
op.GeoM.Translate(state.X+tankSize/2-w/2, state.Y+10)
offset(op)
screen.DrawImage(tankHullImages[state.S], op)
op = &ebiten.DrawImageOptions{}
op.GeoM.Translate(-51, -51)
op.GeoM.Rotate(math.Pi/2 + state.R + state.T)
op.GeoM.Translate(51, 51)
op.GeoM.Translate(float64(state.X), float64(state.Y))
offset(op)
if state.Hit {
op.ColorScale.ScaleWithColor(color.RGBA{128, 128, 128, 255})
}
screen.DrawImage(tankTurretImages[state.S], op)
screen.DrawImage(CooldownImage, op)
}
}

View file

@ -32,6 +32,8 @@ func (b bitfield) Has(flag bitfield) bool {
return b&flag != 0
}
const fireCooldown = 1000
type playerState struct {
X float64
Y float64
@ -56,14 +58,16 @@ type Simulation struct {
rounds [][]*roundState
lastFire []map[int]int
offset int
godMode bool
}
func NewSimulation() *Simulation {
func NewSimulation(godMode bool) *Simulation {
s := &Simulation{}
s.inputs = append(s.inputs, make(map[int]bitfield))
s.states = append(s.states, make(map[int]*playerState))
s.lastFire = append(s.lastFire, make(map[int]int))
s.rounds = append(s.rounds, nil)
s.godMode = godMode
return s
}
@ -121,6 +125,9 @@ ROUNDS:
continue
}
if p.In(image.Rect(int(playerState.X+tankOffset), int(playerState.Y+tankOffset), int(playerState.X+tankSize-tankOffset), int(playerState.Y+tankSize-tankOffset))) {
if s.godMode && player == 0 {
continue
}
playerState.Hit = true
continue ROUNDS
}
@ -146,8 +153,6 @@ ROUNDS:
var dr float64
var dt float64
if !state.Hit {
const fireCooldown = 1000
if input.Has(InputFire) {
lastFire := s.lastFire[s.frame][i]
if lastFire == 0 || s.offset+s.frame-lastFire >= fireCooldown {
@ -202,16 +207,16 @@ ROUNDS:
dx, dy = -float64(speed)*math.Cos(state.R), -float64(speed)*math.Sin(state.R)
case left == 1 && right == 0:
dx, dy = float64(speed*turn)*math.Cos(state.R), float64(speed*turn)*math.Sin(state.R)
dr = -0.01
dr = 0.01
case left == -1 && right == 0:
dx, dy = -float64(speed*turn)*math.Cos(state.R), -float64(speed*turn)*math.Sin(state.R)
dr = 0.01
dr = -0.01
case left == 0 && right == 1:
dx, dy = float64(speed*turn)*math.Cos(state.R), float64(speed*turn)*math.Sin(state.R)
dr = 0.01
dr = -0.01
case left == 0 && right == -1:
dx, dy = -float64(speed*turn)*math.Cos(state.R), -float64(speed*turn)*math.Sin(state.R)
dr = -0.01
dr = 0.01
}
const rotateSpeed = 0.02
@ -279,9 +284,9 @@ func (s *Simulation) Clear(frame int) error {
return nil
}
func (s *Simulation) State() (map[int]*playerState, []*roundState) {
func (s *Simulation) State() (map[int]*playerState, []*roundState, map[int]int) {
if s.frame == 0 {
return s.states[s.frame], s.rounds[s.frame]
return s.states[s.frame], s.rounds[s.frame], s.lastFire[s.frame]
}
return s.states[s.frame-1], s.rounds[s.frame-1]
return s.states[s.frame-1], s.rounds[s.frame-1], s.lastFire[s.frame-1]
}

View file

@ -3,12 +3,11 @@ package main
import (
"log"
"code.rocket9labs.com/tslocum/arcticwarfare/game"
"github.com/hajimehoshi/ebiten/v2"
)
func main() {
err := ebiten.RunGame(game.NewGame())
err := ebiten.RunGame(parseFlags())
if err != nil {
log.Fatal(err)
}