package activities import ( "errors" "log" "time" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" "muhq.space/muhqs-game/go/game" "muhq.space/muhqs-game/go/ui" ) const ( DRAFT_BUTTON_WIDTH = 150 DRAFT_BUTTON_HEIGHT = 40 ) type Draft struct { ui.Collection player *game.Player ctrl game.PlayerControl draft *game.Draft done chan struct{} setInput *ui.TextInput descInput *ui.TextInput deckList *ui.PocList timer *ui.TimerBar deckView *ui.CardGrid } // NewLocalDraft creates a draft activity ready to configure and start a local draft. func NewLocalDraft(width, height int, playerName string) *Draft { d := &Draft{ Collection: ui.Collection{Width: width, Height: height}, done: make(chan struct{}), } d.player = game.NewDraftPlayer(playerName) d.ctrl = game.NewChanPlayerControl(d.player) d.player.Ctrl = d.ctrl d.AddWidget(ui.NewFixedTextBox( (d.Width-DRAFT_BUTTON_WIDTH)/2-DRAFT_BUTTON_WIDTH, (d.Height-DRAFT_BUTTON_HEIGHT)/2-3*DRAFT_BUTTON_HEIGHT, DRAFT_BUTTON_WIDTH, DRAFT_BUTTON_HEIGHT, "sets:", ).Centering(true)) d.setInput = ui.NewTextInput( (d.Width-DRAFT_BUTTON_WIDTH)/2, (d.Height-DRAFT_BUTTON_HEIGHT)/2-3*DRAFT_BUTTON_HEIGHT, DRAFT_BUTTON_WIDTH, DRAFT_BUTTON_HEIGHT, "base,magic,equipments", ) d.AddWidget(d.setInput) d.AddWidget(ui.NewFixedTextBox( (d.Width-DRAFT_BUTTON_WIDTH)/2-DRAFT_BUTTON_WIDTH, (d.Height-DRAFT_BUTTON_HEIGHT)/2-2*DRAFT_BUTTON_HEIGHT, DRAFT_BUTTON_WIDTH, DRAFT_BUTTON_HEIGHT, "draft:", ).Centering(true)) d.descInput = ui.NewTextInput( (d.Width-DRAFT_BUTTON_WIDTH)/2, (d.Height-DRAFT_BUTTON_HEIGHT)/2-2*DRAFT_BUTTON_HEIGHT, DRAFT_BUTTON_WIDTH, DRAFT_BUTTON_HEIGHT, "3x[2;8]", ) d.AddWidget(d.descInput) d.AddWidget(ui.NewRoundSimpleButton( (d.Width-DRAFT_BUTTON_WIDTH)/2+DRAFT_BUTTON_WIDTH+20, (d.Height-DRAFT_BUTTON_HEIGHT)/2-2*DRAFT_BUTTON_HEIGHT, 30, "?", func(*ui.SimpleButton) { ui.OpenUrl("https://muhq.space/muhqs-game/build/en/rules.html#draft-notation") })) d.AddWidget(ui.NewFixedTextBox( (d.Width-DRAFT_BUTTON_WIDTH)/2-DRAFT_BUTTON_WIDTH, (d.Height-DRAFT_BUTTON_HEIGHT)/2-DRAFT_BUTTON_HEIGHT, DRAFT_BUTTON_WIDTH, DRAFT_BUTTON_HEIGHT, "AI:", ).Centering(true)) aiNInput := ui.NewNumberChoice( (d.Width-DRAFT_BUTTON_WIDTH)/2, (d.Height-DRAFT_BUTTON_HEIGHT)/2-DRAFT_BUTTON_HEIGHT, 1, nil, ) d.AddWidget(aiNInput) d.AddWidget(ui.NewSimpleButton( (d.Width-DRAFT_BUTTON_WIDTH)/2, (d.Height-DRAFT_BUTTON_HEIGHT)/2, DRAFT_BUTTON_WIDTH, DRAFT_BUTTON_HEIGHT, "Start", func(*ui.SimpleButton) { d.startDraft(d.descInput.TextOrLabel(), d.setInput.TextOrLabel(), aiNInput.GetChoosen()) })) return d } // StartNewLocalDraft creates a new draft from its inputs and immediatly starts it. // The started draft activiry is returned or any occured error. func StartNewLocalDraft(width, height int, playerName string, desc string, sets string, aiN int) (*Draft, error) { d := &Draft{ Collection: ui.Collection{Width: width, Height: height}, done: make(chan struct{}), } d.player = game.NewDraftPlayer(playerName) d.ctrl = game.NewChanPlayerControl(d.player) d.player.Ctrl = d.ctrl err := d.startDraft(desc, sets, aiN) return d, err } // StartNewRemoteDraft returns a draft activity for the initial received pick notification. func StartNewRemoteDraft(width, height int, ctrl game.PlayerControl, firstPack game.PlayerNotification) *Draft { d := &Draft{ Collection: ui.Collection{Width: width, Height: height}, player: ctrl.Player(), ctrl: ctrl, } d.presentPack(firstPack) return d } func (d *Draft) Update() error { n, err := d.ctrl.RecvNotification() if err != nil { // FIXME } if n.Valid() { log.Println("Received draft pick: %v\n", n) d.presentPack(n) } if d.deckView == nil { select { case <-d.done: d.deckView = ui.NewCardGrid( 0, 0, d.Width-SEALED_DECK_STRIP_WIDTH, d.Height, 0.5, d.player.Deck).Columns(3) d.AddWidget(d.deckView) default: } } if err := ui.Update(); err != nil { return err } if err := d.Collection.Update(); err != nil { return err } if inpututil.IsKeyJustPressed(ebiten.KeyEscape) { PopActivity() } return nil } func (d *Draft) presentPack(n game.PlayerNotification) { pack := n.Context.(game.PileOfCards) grid := ui.NewCardGrid(0, 0, d.Width-SEALED_DECK_STRIP_WIDTH, d.Height, 0.5, pack).Columns(3) selectCard := func(x, y int) { _card := grid.FindObjectAt(x, y) if _card == nil { return } card := _card.(*game.Card) d.ctrl.SendAction(game.NewDraftPick(d.player, pack, card)) d.RemoveWidget(grid) if d.timer != nil { d.timer = nil d.RemoveWidget(d.timer) } } grid.RegisterHandler("click", selectCard) d.AddWidget(grid) if d.draft == nil { // TODO: support any deadline t := time.Duration(pack.Size()*5) * time.Second timer := ui.NewTimerBar(0, 10, d.Width, 5, t) d.AddWidget(timer) } } func (d *Draft) Layout(width, height int) (int, int) { return d.Width, d.Height } func (d *Draft) startDraft(desc string, setList string, aiN int) error { log.Printf("starting %s draft from '%s'\n", desc, setList) var sets []game.SetIdentifier var err error if sets, err = game.SetListToSets(setList); err != nil { return err } d.draft, err = game.NewDraft([]*game.Player{d.player}, desc, sets) if err != nil { return err } for _ = range aiN { d.draft.AddRandomAi() } if !d.draft.Valid() { return game.ErrInvalidDraft } d.Clear() d.deckList = ui.NewPocList( d.Width-SEALED_DECK_STRIP_WIDTH/2, d.Height-DRAFT_BUTTON_HEIGHT-20, d.player.Deck, ) d.deckList.Bg(ui.Gray) { cardsInDeck := 0 d.deckList.RegisterHandler("update", func() error { if n := d.player.Deck.Size(); n > cardsInDeck { d.deckList.ForceRedraw() cardsInDeck = n } return nil }) } d.AddWidget(d.deckList) d.AddWidget(ui.NewSimpleButton( d.Width-SEALED_DECK_STRIP_WIDTH/2-DRAFT_BUTTON_WIDTH/2, d.Height-DRAFT_BUTTON_HEIGHT, DRAFT_BUTTON_WIDTH, DRAFT_BUTTON_HEIGHT, "Done", func(*ui.SimpleButton) { PopActivity() })) go func() { d.draft.Run() close(d.done) }() return nil } func (d *Draft) startDraftSafe(desc string, setList string, aiN int) { err := d.startDraft(desc, setList, aiN) if err == nil { return } var descErr game.ErrParseDraftDesc if errors.Is(err, game.ErrUnknownSet) { d.setInput.Bg(ui.WarningBg) d.setInput.SetInput("") } else if errors.As(err, &descErr) { d.descInput.Bg(ui.WarningBg) d.descInput.SetInput("") } else if errors.Is(err, game.ErrInvalidDraft) { d.descInput.Bg(ui.WarningBg) d.descInput.SetInput("") } } // GetDeckList returns the player's current deck list. func (d *Draft) GetDeckList() string { return d.player.Deck.ToList() }