Files
metro-puzzle-jp/internal/game/minigame_view.go
claude f04d6a88f9
All checks were successful
release / build-release (push) Successful in 2m7s
M10c: vem betalar i kiosken och spärrar utan återvändo
Björns tillägg 2026-10-24. Med egna kort och fler i sällskapet frågar
kiosken vem som betalar (minigame.Payer: kontant, spelarens kort eller en
följares kort; den som inte har råd nekas, Esc avbryter). Avgiften dras
nu när man passerar spärren in i det betalda området (level.InsideGates),
inte på spärrrutan. Banflaggan oneWayGates: incheckad släpps inte ut igen
förrän man åkt; Enter vid spärren kallar på personal (10 s) som låser upp
korten; ny inpassering kostar igen. På i 005, 007/007b och 008/008b.
Skriptkommandon urge och push. Docs, wiki och skärmdumpar uppdaterade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 22:12:13 +02:00

329 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package game
import (
"fmt"
"image"
"image/color"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/lang"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/minigame"
)
// Minispelen ritas som en panel över banan. Logiken bor i internal/minigame.
func (s *LevelScene) drawMinigame(g *Game, screen *ebiten.Image) {
a := g.Assets
r := s.run
panel := image.Rect(12, 28, ScreenW-12, ScreenH-10)
vector.DrawFilledRect(screen, float32(panel.Min.X), float32(panel.Min.Y), float32(panel.Dx()), float32(panel.Dy()), colPanel, false)
vector.DrawFilledRect(screen, float32(panel.Min.X), float32(panel.Min.Y), float32(panel.Dx()), 3, colKnown, false)
switch m := r.Mini.(type) {
case *minigame.TopUp:
s.drawTopUp(a, screen, m, panel)
case *minigame.Ticket:
s.drawTicket(a, screen, m, panel)
case *minigame.Crowd:
s.drawCrowd(a, screen, m, panel)
case *minigame.Ride:
s.drawRide(a, screen, m, panel)
case *minigame.CarPick:
s.drawCarPick(a, screen, m, panel)
case *minigame.Payer:
s.drawPayer(a, screen, m, panel)
}
}
// drawPayer: vem betalar i kiosken – kontant, spelarens kort eller en
// följares kort, med saldon; de som inte har råd i rött.
func (s *LevelScene) drawPayer(a *Assets, screen *ebiten.Image, m *minigame.Payer, p image.Rectangle) {
x, y := p.Min.X+8, p.Min.Y+8
a.DrawBig(screen, "支払い", x, y, colKnown)
a.Draw(screen, a.T("mini.payer.title"), x+60, y+2, colText)
y += 20
a.Draw(screen, fmt.Sprintf(a.T("mini.payer.price"), m.Price), x, y, colDim)
y += LineH + 4
for i, o := range m.Options {
name, kind := o.Name, a.T("mini.payer.card")
if o.Cash {
name, kind = a.T("mini.payer.cash"), ""
}
col := colText
if !m.CanAfford(i) {
col = colBad
}
if i == m.Cursor {
vector.DrawFilledRect(screen, float32(x-2), float32(y+i*LineH-1), 220, LineH, colBar, false)
if m.CanAfford(i) {
col = colKnown
}
}
a.Draw(screen, fmt.Sprintf("%-8s %5d¥ %s", name, o.Yen, kind), x, y+i*LineH, col)
}
y += len(m.Options)*LineH + 4
if m.Refused {
a.Draw(screen, a.T("mini.payer.short"), x, y, colBad)
}
a.Draw(screen, a.T("mini.payer.hint"), x, p.Max.Y-14, colDim)
}
// drawCarPick: vagnarna i rad, rosa för kvinnovagn, figurer för trängsel,
// kömarkeringar under, och en fas där folk kliver av först.
func (s *LevelScene) drawCarPick(a *Assets, screen *ebiten.Image, m *minigame.CarPick, p image.Rectangle) {
x, y := p.Min.X+8, p.Min.Y+8
a.DrawBig(screen, "乗車", x, y, colKnown)
a.Draw(screen, a.T("mini.car.title"), x+36, y+2, colText)
y += 24
if m.Alighting > 0 {
a.Draw(screen, a.T("mini.car.alighting"), x, y, colWarn)
w := float32(p.Dx() - 16)
vector.DrawFilledRect(screen, float32(x), float32(y+LineH+2), w, 4, colBar, false)
vector.DrawFilledRect(screen, float32(x), float32(y+LineH+2), w*float32(1-m.Alighting/minigame.AlightSec), 4, colKnown, false)
} else {
a.Draw(screen, a.T("mini.car.pick"), x, y, colDim)
}
y += LineH + 12
carW := min(64, (p.Dx()-16)/max(1, len(m.Cars))-4)
for i, c := range m.Cars {
cx := x + i*(carW+4)
body := color.RGBA{230, 234, 240, 255}
if c.WomenOnly {
body = color.RGBA{255, 160, 205, 255}
}
vector.DrawFilledRect(screen, float32(cx), float32(y), float32(carW), 30, body, false)
vector.DrawFilledRect(screen, float32(cx), float32(y+30), float32(carW), 3, colBad, false)
if i == m.Cursor && m.Alighting <= 0 {
vector.DrawFilledRect(screen, float32(cx), float32(y-4), float32(carW), 2, colKnown, false)
}
a.Draw(screen, fmt.Sprintf("%d%s", c.Index, a.T("hud.car_suffix_ja")), cx+3, y+2, colPanel)
// figurer = trängsel
for k := 0; k <= c.Crowd; k++ {
px := cx + 4 + k*7
vector.DrawFilledRect(screen, float32(px), float32(y+16), 4, 10, color.RGBA{60, 70, 100, 255}, false)
vector.DrawFilledRect(screen, float32(px), float32(y+13), 4, 3, color.RGBA{240, 200, 160, 255}, false)
}
if c.WomenOnly {
a.Draw(screen, "女性専用", cx+2, y+34, color.RGBA{255, 150, 200, 255})
} else {
a.Draw(screen, a.T("car.crowd."+minigame.CrowdNames[c.Crowd]), cx+2, y+34, colDim)
}
// kömarkering under vagnen
vector.DrawFilledRect(screen, float32(cx+carW/2-6), float32(y+48), 12, 2, colWarn, false)
vector.DrawFilledRect(screen, float32(cx+carW/2-1), float32(y+50), 2, 4, colWarn, false)
}
if s.run.Party.HasMen() {
a.Draw(screen, a.T("mini.car.men"), x, p.Max.Y-28, colWarn)
}
a.Draw(screen, a.T("mini.car.hint"), x, p.Max.Y-16, colDim)
}
// jp ritar japansk text sedd genom spelarens kunskap (kända ord i gult).
func (s *LevelScene) jp(a *Assets, screen *ebiten.Image, text string, x, y int) {
rd := lang.Render(a.Dict, s.run.Know, text, UILang)
a.drawLines(screen, []line{shownLine(rd.Original, colText, false)}, x, y)
}
func (s *LevelScene) drawTopUp(a *Assets, screen *ebiten.Image, m *minigame.TopUp, p image.Rectangle) {
x, y := p.Min.X+8, p.Min.Y+8
a.DrawBig(screen, "チャージ", x, y, colKnown)
a.Draw(screen, a.T("mini.topup.title"), x+60, y+2, colText)
y += 20
switch m.Stage {
case minigame.TopUpPickMember:
a.Draw(screen, a.T("mini.topup.member"), x, y, colDim)
y += LineH + 4
for i, name := range m.Members {
col := colText
if i == m.Cursor {
vector.DrawFilledRect(screen, float32(x-2), float32(y+i*LineH-1), 200, LineH, colBar, false)
col = colKnown
}
bal := s.run.Wallet.Card
if i > 0 {
bal = s.run.Party.Members[i].Card
}
a.Draw(screen, fmt.Sprintf("%-8s %5d¥", name, bal), x, y+i*LineH, col)
}
case minigame.TopUpPickButton:
a.Draw(screen, a.T("mini.topup.pick"), x, y, colDim)
y += LineH + 4
for i, b := range m.Buttons {
bx, by := x+(i%2)*140, y+(i/2)*26
col := colBar
if i == m.Cursor {
col = color.RGBA{90, 105, 136, 255}
}
vector.DrawFilledRect(screen, float32(bx), float32(by), 132, 22, col, false)
s.jp(a, screen, b, bx+6, by+5)
}
if m.Penalty > 0 {
a.Draw(screen, "ピッ! "+a.T("mini.topup.wrong"), x, y+56, colBad)
}
case minigame.TopUpPickAmount:
a.Draw(screen, a.T("mini.topup.amount"), x, y, colDim)
y += LineH + 4
for i, amt := range m.Amounts {
col := colText
if i == m.Cursor {
vector.DrawFilledRect(screen, float32(x-2), float32(y+i*LineH-1), 120, LineH, colBar, false)
col = colKnown
}
a.Draw(screen, fmt.Sprintf("%d円 (%d¥)", amt, amt), x, y+i*LineH, col)
}
case minigame.TopUpFeed:
a.Draw(screen, fmt.Sprintf(a.T("mini.topup.feed"), m.Amount), x, y, colText)
y += LineH + 8
vector.DrawFilledRect(screen, float32(x), float32(y), float32(p.Dx()-16), 8, colBar, false)
vector.DrawFilledRect(screen, float32(x), float32(y), float32(float64(p.Dx()-16)*m.Fill), 8, colGood, false)
}
a.Draw(screen, a.T("mini.cancel"), x, p.Max.Y-16, colDim)
}
func (s *LevelScene) drawTicket(a *Assets, screen *ebiten.Image, m *minigame.Ticket, p image.Rectangle) {
x, y := p.Min.X+8, p.Min.Y+8
a.DrawBig(screen, "きっぷ", x, y, colKnown)
a.Draw(screen, a.T("mini.ticket.title"), x+48, y+2, colText)
y += 20
if m.Stage == minigame.TicketPickSeat {
s.drawSeats(a, screen, m, p, x, y)
return
}
a.Draw(screen, fmt.Sprintf(a.T("mini.ticket.hint"), s.run.Level.Destination.Get("romaji"), m.Cash), x, y, colDim)
y += LineH + 4
for i, o := range m.Options {
ry := y + i*LineH
col := colText
if i == m.Cursor {
vector.DrawFilledRect(screen, float32(x-2), float32(ry-1), float32(p.Dx()-12), LineH, colBar, false)
}
if o.Fare > m.Cash {
col = colDim
}
s.jp(a, screen, o.Dest, x, ry)
fare := fmt.Sprintf("%d円", o.Fare)
a.Draw(screen, fare, p.Max.X-10-a.Width(fare), ry, col)
}
a.Draw(screen, a.T("mini.cancel"), x, p.Max.Y-16, colDim)
}
func (s *LevelScene) drawCrowd(a *Assets, screen *ebiten.Image, m *minigame.Crowd, p image.Rectangle) {
x, y := p.Min.X+8, p.Min.Y+8
a.DrawBig(screen, "満員", x, y, colBad)
a.Draw(screen, a.T("mini.crowd.title"), x+36, y+2, colText)
y += 24
a.Draw(screen, fmt.Sprintf(a.T("mini.crowd.hint"), m.MaxTries-m.Attempts), x, y, colDim)
y += LineH + 10
w := float32(p.Dx() - 16)
vector.DrawFilledRect(screen, float32(x), float32(y), w, 14, colBar, false)
vector.DrawFilledRect(screen, float32(x)+w*float32(m.ZoneLo), float32(y), w*float32(m.ZoneHi-m.ZoneLo), 14, colGood, false)
vector.DrawFilledRect(screen, float32(x)+w*float32(m.Pos)-1, float32(y)-3, 3, 20, colText, false)
}
func (s *LevelScene) drawRide(a *Assets, screen *ebiten.Image, m *minigame.Ride, p image.Rectangle) {
x, y := p.Min.X+8, p.Min.Y+8
a.DrawBig(screen, "次は", x, y, colDim)
a.Draw(screen, a.T("mini.ride.title"), x+36, y+2, colText)
y += 22
cur := m.Current()
a.DrawBig(screen, cur, x, y, colText)
rd := lang.Render(a.Dict, s.run.Know, cur, UILang)
a.drawLines(screen, []line{shownLine(rd.Partial, colText, false)}, x+a.Width(cur)*12/10+12, y+2)
lvl := s.run.Level
if lang.ShowEnglish(lvl.ID, m.Index, 77, lvl.Difficulty) {
if e := a.Dict.Lookup(cur); e != nil {
a.Draw(screen, e.Romaji, x, y+16, colEnglish)
}
}
y += 34
// Linjen: en prick per station, den aktuella större.
for i := range m.Stops {
cx := x + 6 + i*22
col := colDim
if i == m.Index {
col = colText
}
if i < m.Index {
col = colBar
}
vector.DrawFilledRect(screen, float32(cx), float32(y+3), 14, 2, colBar, false)
vector.DrawFilledRect(screen, float32(cx-3), float32(y), 6, 8, col, false)
}
y += 14
vector.DrawFilledRect(screen, float32(x), float32(y), float32(p.Dx()-16), 3, colBar, false)
vector.DrawFilledRect(screen, float32(x), float32(y), float32(float64(p.Dx()-16)*(1-m.Timer/m.StopSec)), 3, colKnown, false)
hint := a.T("mini.ride.hint")
if dest := lvl.Destination.Get("romaji"); dest != "" {
hint = strings.Replace(hint, "%s", dest, 1)
}
if m.NeedsLetPass() {
a.Draw(screen, a.T("mini.ride.let_pass"), x, p.Max.Y-30, colBad)
} else if m.Crowd > 0 {
a.Draw(screen, fmt.Sprintf("%s: %s", a.T("mini.ride.crowd"), a.T("car.crowd."+minigame.CrowdNames[m.Crowd])), x, p.Max.Y-30, colDim)
}
a.Draw(screen, hint, x, p.Max.Y-16, colGood)
}
// prefText beskriver ett önskemål.
func prefText(a *Assets, m *minigame.Ticket, p minigame.SeatPref) string {
switch p.Kind {
case minigame.PrefWindow:
return a.T("seat.window")
case minigame.PrefAisle:
return a.T("seat.aisle")
case minigame.PrefLeft:
return a.T("seat.left")
case minigame.PrefRight:
return a.T("seat.right")
default:
return fmt.Sprintf(a.T("seat.with"), m.Members[p.With])
}
}
// drawSeats ritar vagnen: tre rader, fönster V | gång | fönster H, och
// sällskapets önskemål till höger.
func (s *LevelScene) drawSeats(a *Assets, screen *ebiten.Image, m *minigame.Ticket, p image.Rectangle, x, y int) {
a.Draw(screen, fmt.Sprintf(a.T("mini.seat.hint"), m.Members[m.Current]), x, y, colDim)
y += LineH + 4
const sw, sh, gap = 22, 18, 10
a.Draw(screen, a.T("seat.front"), x+2, y-2, colDim)
y += 10
for row := 0; row < minigame.SeatRows; row++ {
for col := 0; col < minigame.SeatCols; col++ {
sx := x + col*(sw+2)
if col >= 2 {
sx += gap
}
sy := y + row*(sh+3)
seat := minigame.Seat{Row: row, Col: col}
bg := colBar
if m.SeatTaken(seat) {
bg = color.RGBA{90, 105, 136, 255}
}
vector.DrawFilledRect(screen, float32(sx), float32(sy), sw, sh, bg, false)
if seat == m.Seat {
vector.DrawFilledRect(screen, float32(sx), float32(sy), sw, 2, colKnown, false)
vector.DrawFilledRect(screen, float32(sx), float32(sy+sh-2), sw, 2, colKnown, false)
}
for i, as := range m.Assigned {
if as == seat {
a.Draw(screen, string([]rune(m.Members[i])[:1]), sx+8, sy+3, colText)
}
}
}
}
// Önskemål.
px := x + 4*(sw+2) + gap + 14
a.Draw(screen, a.T("mini.seat.prefs"), px, y-10, colDim)
for i, pr := range m.Prefs {
col := colText
if pr.Member == m.Current {
col = colKnown
}
a.Draw(screen, m.Members[pr.Member]+": "+prefText(a, m, pr), px, y+i*LineH, col)
}
a.Draw(screen, a.T("mini.cancel"), x, p.Max.Y-16, colDim)
}