package main import ( "fmt" "image/color" "log" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/inpututil" "muhq.space/muhqs-game/go/assets" "muhq.space/muhqs-game/go/game" "muhq.space/muhqs-game/go/ui" ) const ( screenWidth = 1280 screenHeight = 1024 DEFAULT_BUTTON_HEIGHT = 40 RESOLVE_BUTTON_X = 500 STACK_BUFFER_WIDTH = 300 HOVER_THRESHOLD = 60 ) type HandLayer struct{} func (l *HandLayer) Draw(screen *ebiten.Image) { cardImg := assets.GetCard("base/archer", "en") op := &ebiten.DrawImageOptions{} op.GeoM.Translate(0, float64(screenHeight)) op.GeoM.Scale(0.5, 0.5) screen.DrawImage(cardImg, op) } var ( playerGreen = color.RGBA{1, 0xff, 1, 0xff} playerBlue = color.RGBA{1, 1, 0xff, 0xff} ) type hoverDetector struct { last_x, last_y int ticks int resetFunc func() } func (h *hoverDetector) update(x, y int) { if x == h.last_x && y == h.last_y { h.ticks++ } else { h.last_x, h.last_y = x, y h.reset() } } func (h *hoverDetector) reset() { h.ticks = 0 if h.resetFunc != nil { h.resetFunc() h.resetFunc = nil } } func (h *hoverDetector) startedHovering() bool { return h.ticks == HOVER_THRESHOLD } type Game struct { gameState *game.State handLayer *HandLayer mapView *ui.MapView resolveButton *ui.SimpleButton stackBuffer *ui.Buffer widgets []ui.Widget selectedObject interface{} hoverDetector hoverDetector } func NewGame() *Game { g := &Game{} g.widgets = []ui.Widget{} g.gameState = game.NewState() g.resolveButton = ui.NewSimpleButton(screenWidth-125, screenHeight-DEFAULT_BUTTON_HEIGHT, 125, DEFAULT_BUTTON_HEIGHT, func(*ui.SimpleButton) { g.resolveAction() }, "Resolve") g.addWidget(g.resolveButton) g.stackBuffer = ui.NewBuffer(screenWidth-STACK_BUFFER_WIDTH, g.resolveButton.Y-400, STACK_BUFFER_WIDTH, 400) return g } func NewGameFromMap(mapName string) *Game { g := NewGame() g.loadMap(mapName) return g } func (g *Game) loadMap(mapName string) { m, err := game.GetMap(mapName) if err != nil { log.Fatal(err) } g.gameState.Map = m g.mapView = ui.NewMapView(g.gameState) } func (g *Game) addPlayer(name string, deck *game.Deck, color color.Color) { g.gameState.AddNewPlayer(name, deck, color) } func (g *Game) getPlayer(name string) *game.Player { for _, p := range g.gameState.Players { if p.Name == name { return p } } return nil } func (g *Game) addWidget(w ui.Widget) { g.widgets = append(g.widgets, w) } func (g *Game) removeWidget(widget ui.Widget) { nwidgets := len(g.widgets) for i, w := range g.widgets { if w != widget { continue } g.widgets[i] = g.widgets[nwidgets-1] g.widgets = g.widgets[:nwidgets-1] return } } func (g *Game) clearMapHighlights() { g.mapView.ClearPermanentsHighlights() g.mapView.ClearTileHighlights() g.mapView.ForceRedraw() } func (g *Game) declareAction(a game.Action) { a.Declare(g.gameState) g.clearMapHighlights() g.stackBuffer.AddLine(a.String()) } func (g *Game) resolveAction() { if g.gameState.Stack.IsEmpty() { return } g.gameState.Stack.Pop() g.clearMapHighlights() g.stackBuffer.RemoveLast() } func (g *Game) findObjectAt(x, y int) interface{} { for _, b := range g.widgets { if obj := b.FindObjectAt(x, y); obj != nil { return obj } } if obj := g.mapView.FindObjectAt(x, y); obj != nil { return obj } return nil } func (g *Game) Update() error { x, y := ebiten.CursorPosition() g.hoverDetector.update(x, y) obj := g.findObjectAt(x, y) if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { if obj == nil { fmt.Printf("No object found at cursor position (%d, %d)\n", x, y) return nil } log.Printf("Active object is of type %T\n", g.selectedObject) switch obj := obj.(type) { case ui.Button: obj.Click(x, y) case *game.Tile: tile := obj if g.selectedObject != nil { switch obj := g.selectedObject.(type) { case *game.MoveActionPrototype: action := obj.Specialize(tile) g.selectedObject = nil g.declareAction(action) g.mapView.HighlightTile(tile) } } else { g.mapView.HighlightTile(tile) } case game.Permanent: perm := obj if g.selectedObject == nil { actions := perm.GetAvailActionPrototypes() if len(actions) > 0 { g.selectedObject = perm onClick := func(c *ui.Choice, x, y int) { g.removeWidget(c) g.selectedObject = actions[c.GetChoosen(x, y)] } g.addWidget(ui.NewActionChoice(500, 200, actions, onClick)) } } else { switch obj := g.selectedObject.(type) { case *game.AttackActionPrototype: action := obj.Specialize(perm) g.selectedObject = nil g.declareAction(action) g.mapView.HighlightPermanent(perm) } } default: log.Fatalf("Object of type %T not handled", obj) } switch obj := g.selectedObject.(type) { case *game.MoveActionPrototype: g.mapView.HighlightTiles(obj.Unit.MoveRangeTiles(g.gameState.Map)) case *game.AttackActionPrototype: perms := obj.Unit.AttackablePermanents(g.gameState.Map) g.mapView.HighlightPermanents(perms) } } else if g.hoverDetector.startedHovering() && g.selectedObject == nil && obj != nil { var hoverHint *ui.TextBox switch obj := obj.(type) { case *game.Unit: hoverHint = ui.NewUnitInfo(x+ui.TILE_WIDTH, y, obj) case game.Permanent: hoverHint = ui.NewPermInfo(x+ui.TILE_WIDTH, y, obj) } if hoverHint != nil { g.addWidget(hoverHint) g.hoverDetector.resetFunc = func() { g.removeWidget(hoverHint) } } } else if inpututil.IsKeyJustPressed(ebiten.KeySpace) { g.resolveAction() } else if inpututil.IsKeyJustPressed(ebiten.KeyEscape) { g.selectedObject = nil g.hoverDetector.reset() g.clearMapHighlights() } return nil } func (g *Game) Draw(screen *ebiten.Image) { g.handLayer.Draw(screen) g.mapView.Draw(screen) for _, b := range g.widgets { b.Draw(screen) } if !g.gameState.Stack.IsEmpty() { g.stackBuffer.Draw(screen) } ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f FPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS())) } func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { return screenWidth, screenHeight } func main() { ui.InitFont() ebiten.SetWindowSize(screenWidth, screenHeight) ebiten.SetWindowTitle("Muhq's Game") g := NewGameFromMap("2P-ring-street") g.addPlayer("muhq", game.NewDeckFromDeckList(g.gameState.Map.StartDeckList), playerBlue) muhq := g.getPlayer("muhq") g.gameState.AddNewUnit("base/archer", game.Position{X: 0, Y: 0}, muhq) g.addPlayer("enemy", game.NewDeckFromDeckList(g.gameState.Map.StartDeckList), playerGreen) enemy := g.getPlayer("enemy") g.gameState.AddNewArtifact("base/palisade", game.Position{X: 1, Y: 1}, enemy) if err := ebiten.RunGame(g); err != nil { log.Fatal(err) } }