package game import ( "client/client" "client/elements" "client/fonts" "client/gamedata" "client/pb" "fmt" "image/color" "maps" "math" "sync" "time" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/text/v2" "github.com/hajimehoshi/ebiten/v2/vector" "golang.org/x/exp/rand" ) const ( screenWidth = 640 screenHeight = 480 movementLimit = 5 stageRadius = 200 ) var ( namelist = []string{"slappy", "mick", "rodney", "george", "ringo", "robin", "temitry", "evangeline", "ron", "abigail", "lester", "maynard", "agnes", "stacey", "wendell", "susanne", "myrtle", "teresa", "kristi", "genos", "felton", "lawrence", "rosie", "nigel", "constance", "maryellen", "dollie", "markus", "dorthy", "lazaro", "willa", "dino", "gustavo", "conrad", "georgia", "lucinda", "saitama"} ) func init() { rand.Seed(uint64(time.Now().UnixNano())) } type ClientData struct { Id int Address string Name string Position gamedata.Coordinates Hit bool Eliminated bool } type Game struct { name string blocky *elements.Block hitblocky *elements.Block elimblocky *elements.Block gameId int realclients map[int]ClientData gameclient *client.Client cycle int mu sync.Mutex //similar fields that we see in the client list, but for us eliminated bool hit bool dino *elements.Dino } func NewGame() *Game { g := &Game{ gameclient: client.NewClient(), blocky: elements.NewBlock(), hitblocky: elements.NewBlock(), elimblocky: elements.NewBlock(), cycle: 0, name: namelist[rand.Intn(len(namelist))], dino: elements.NewDino(gamedata.DinoTypeGreen), } g.blocky.SetColor(color.RGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}) g.hitblocky.SetColor(color.RGBA{R: 0x00, G: 0xff, B: 0xff, A: 0xff}) g.elimblocky.SetColor(color.RGBA{R: 0x00, G: 0x00, B: 0xff, A: 0xff}) g.blocky.SetPosition(gamedata.Coordinates{X: float64(screenWidth) / 2, Y: float64(screenHeight) / 2}) g.blocky.SetTargetPosition(gamedata.Coordinates{X: float64(screenWidth) / 2, Y: float64(screenHeight) / 2}) g.realclients = make(map[int]ClientData) //g.gameId = g.gameclient.GetIdentity() go g.gameclient.ReadData(g.HandleServerData) return g } func (g *Game) Update() error { g.blocky.Update() g.dino.Update() //g.hitblocky.Update() g.HandleInput() g.SendPosition() g.cycle++ return nil } func (g *Game) Draw(screen *ebiten.Image) { screen.Clear() g.blocky.Draw() g.hitblocky.Draw() g.elimblocky.Draw() vector.StrokeCircle(screen, float32(screenWidth)/2, float32(screenHeight)/2, stageRadius, 3, color.White, true) op := &ebiten.DrawImageOptions{} op.GeoM.Translate(-float64(g.blocky.Sprite.Bounds().Dx())/2, -float64(g.blocky.Sprite.Bounds().Dy())/2) op.GeoM.Translate(g.blocky.GetPosition().X, g.blocky.GetPosition().Y) if !g.eliminated { if !g.hit { screen.DrawImage(g.blocky.Sprite, op) } else { screen.DrawImage(g.hitblocky.Sprite, op) } } else { screen.DrawImage(g.elimblocky.Sprite, op) } f2 := &text.GoTextFace{ Source: fonts.LaunchyFont.New, Size: 12, } top := &text.DrawOptions{} top.GeoM.Translate(g.blocky.GetPosition().X-50, g.blocky.GetPosition().Y+15) text.Draw(screen, "you ("+g.name+")", f2, top) g.mu.Lock() clientcopy := maps.Clone(g.realclients) g.mu.Unlock() for _, client := range clientcopy { op := &ebiten.DrawImageOptions{} op.GeoM.Translate(-float64(g.blocky.Sprite.Bounds().Dx())/2, -float64(g.blocky.Sprite.Bounds().Dy())/2) op.GeoM.Translate(client.Position.X, client.Position.Y) if client.Eliminated { screen.DrawImage(g.elimblocky.Sprite, op) } else { if !client.Hit { screen.DrawImage(g.blocky.Sprite, op) } else { screen.DrawImage(g.hitblocky.Sprite, op) } } f2 := &text.GoTextFace{ Source: fonts.LaunchyFont.New, Size: 12, } top := &text.DrawOptions{} top.GeoM.Translate(client.Position.X, client.Position.Y) text.Draw(screen, client.Name, f2, top) } g.dino.Draw() dop := &ebiten.DrawImageOptions{} dop.GeoM.Translate(-float64(g.dino.Sprite.Bounds().Dx())/2, -float64(g.dino.Sprite.Bounds().Dy())/2) dop.GeoM.Translate(g.blocky.GetPosition().X, g.blocky.GetPosition().Y) screen.DrawImage(g.dino.Sprite, dop) } func (g *Game) Layout(outsideWidth, outsideHeight int) (screenwidth, screenheight int) { return screenWidth, screenHeight } func (g *Game) HandleServerData(envelope *pb.ServerEnvelope) { switch payload := envelope.Payload.(type) { case *pb.ServerEnvelope_Broadcast: //fmt.Println("Here comes the broadcast!") for _, client := range payload.Broadcast.Clients { if client.Id != int32(g.gameId) { update := ClientData{ Id: int(client.Id), Address: client.Address, Name: client.Name, Position: gamedata.Coordinates{ X: client.Coordinates.X, Y: client.Coordinates.Y, }, Hit: client.Hit, Eliminated: client.Eliminated, } g.mu.Lock() g.realclients[int(client.Id)] = update g.mu.Unlock() } else { g.eliminated = client.Eliminated g.hit = client.Hit //this is us //g.blocky.SetHit(client.Hit) } } case *pb.ServerEnvelope_Event: //add or remove client from client list if payload.Event.Connected && payload.Event.Id != int32(g.gameId) { realclient := ClientData{ Id: int(payload.Event.Id), } g.realclients[int(payload.Event.Id)] = realclient } else { delete(g.realclients, int(payload.Event.Id)) } case *pb.ServerEnvelope_Identity: fmt.Println("Server is trying to give us our id: ", payload.Identity.Id) g.gameId = int(payload.Identity.Id) case *pb.ServerEnvelope_Gameevent: //fmt.Printf("someone slapping! target:%d, instigator:%d isSlap:%d", payload.Gameevent.Target, payload.Gameevent.Instigator, payload.Gameevent.Slap) switch payload.Gameevent.Event.(type) { case *pb.GameEvent_Slap: if payload.Gameevent.Target == int32(g.gameId) { g.mu.Lock() dx := g.blocky.GetPosition().X - g.realclients[int(payload.Gameevent.Instigator)].Position.X dy := g.blocky.GetPosition().Y - g.realclients[int(payload.Gameevent.Instigator)].Position.Y g.mu.Unlock() if dx != 0 { dx = (dx / math.Abs(dx)) * 100 } if dy != 0 { dy = (dy / math.Abs(dy)) * 100 } if dx == 0 && dy == 0 { b := rand.Intn(2) if b == 0 { dx = 100 dy = 100 } else { dx = -100 dy = -100 } } cpos := gamedata.Coordinates{} cpos.X = g.blocky.GetPosition().X + dx cpos.Y = g.blocky.GetPosition().Y + dy g.blocky.SetTargetPosition(cpos) } case *pb.GameEvent_Eliminated: fmt.Println("someone eliminated...", payload.Gameevent.Target) /* g.mu.Lock() client := g.realclients[int(payload.Gameevent.Target)] client.Eliminated = true g.realclients[int(payload.Gameevent.Target)] = client g.mu.Unlock() */ } } } func (g *Game) HandleInput() { dx := 0 dy := 0 if ebiten.IsKeyPressed(ebiten.KeyW) { dy = -movementLimit } if ebiten.IsKeyPressed(ebiten.KeyS) { dy = +movementLimit } if ebiten.IsKeyPressed(ebiten.KeyA) { dx = -movementLimit g.dino.SetLeft(true) } if ebiten.IsKeyPressed(ebiten.KeyD) { dx = +movementLimit g.dino.SetLeft(false) } if math.Abs(float64(dx)) > 0 || math.Abs(float64(dy)) > 0 { g.dino.SetAction(gamedata.DinoActionWalk) } else { g.dino.SetAction(gamedata.DinoActionIdle) } cpos := g.blocky.GetPosition() cpos.X += float64(dx) cpos.Y += float64(dy) g.blocky.SetTargetPosition(cpos) if inpututil.IsKeyJustPressed(ebiten.KeySpace) { g.SendSlap() g.dino.SetAction(gamedata.DinoActionSlap) } } func (g *Game) SendPosition() { //broadcast our position if g.gameclient.IsConnected() { cd := &pb.ClientCoordinates{ Name: g.name, Coordinates: &pb.Coordinates{ X: g.blocky.GetPosition().X, //g.position.X, Y: g.blocky.GetPosition().Y, //g.position.Y, }, } envelope := &pb.ClientEnvelope{ Payload: &pb.ClientEnvelope_Coordinates{ Coordinates: cd, }, } g.gameclient.SendMessage(envelope) } } func (g *Game) SendSlap() { if g.gameclient.IsConnected() { slap := &pb.SlapEvent{ Slap: true, } envelope := &pb.ClientEnvelope{ Payload: &pb.ClientEnvelope_Slap{ Slap: slap, }, } g.gameclient.SendMessage(envelope) } }