Files
metro-puzzle-jp/internal/game/minigame_view.go
claude de61568e44
All checks were successful
release / build-release (push) Successful in 1m33s
M9b: folk, filer, reklam, sorl och vagnval
Folk på stationen (NPC:er som vandrar, kliver av tåg och går mot
utgången, blockerar spelaren men inte varandra), vänster-/högerfiler
som bromsar på fel sida, bredare gångar, reklamaffischer (clutter),
japanskt sorl i loop (noise), vagnval med kvinnovagn (stort avdrag med
man i sällskapet, tidsstyrd), kömarkeringar på perrongen, trängsel per
vagn (bonus för tom vagn, svettig-debuff och svårare resa i full vagn,
släpp förbi passagerare) och att folk kliver av innan man får gå på.
Ny bana 005 Ueno. Skärmdumpsverktyget kan filtrera på bana/skript.

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

294 lines
11 KiB
Go

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)
}
}
// 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)
}