Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

views.gno

6.69 Kb · 205 lines
  1package shots
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/penalty/v0"
  8	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/shootout/v0"
  9)
 10
 11// ---------------------------------------------------------------- JSON
 12
 13func q(s string) string { return strconv.Quote(s) }
 14
 15func kv(k, v string) string { return q(k) + ":" + v }
 16
 17func obj(fields ...string) string { return "{" + strings.Join(fields, ",") + "}" }
 18
 19func gameJSON(g shootout.Game) string {
 20	return obj(
 21		kv("id", strconv.FormatUint(g.ID, 10)),
 22		kv("creator", q(string(g.Creator))),
 23		kv("status", q(g.Status.String())),
 24		kv("pot", itoa(g.Pot)),
 25		kv("entryFee", itoa(g.EntryFee)),
 26		kv("timeoutBlocks", itoa(g.TimeoutBlocks)),
 27		kv("config", q(g.Config.String())),
 28		kv("sessions", strconv.Itoa(g.Sessions)),
 29		kv("winner", q(string(g.Winner))),
 30		kv("createdHeight", itoa(g.CreatedHeight)),
 31		kv("closedHeight", itoa(g.ClosedHeight)),
 32	)
 33}
 34
 35func resultJSON(r penalty.Result) string {
 36	return obj(
 37		kv("outcome", q(r.Outcome.String())),
 38		kv("x", itoa(r.X)),
 39		kv("y", itoa(r.Y)),
 40		kv("arrivalMs", itoa(r.ArrivalMS)),
 41		kv("ballSpeed", itoa(r.BallSpeed)),
 42		kv("mishitX", itoa(r.MishitX)),
 43		kv("mishitY", itoa(r.MishitY)),
 44		kv("keeper", q(r.Keeper.String())),
 45	)
 46}
 47
 48func shotJSON(s penalty.ShotInput) string {
 49	return obj(kv("aimX", itoa(s.AimX)), kv("aimY", itoa(s.AimY)), kv("contactX", itoa(s.ContactX)),
 50		kv("contactY", itoa(s.ContactY)), kv("power", itoa(s.Power)))
 51}
 52
 53func planJSON(p penalty.KeeperPlan) string {
 54	return obj(kv("direction", strconv.Itoa(int(p.Direction))), kv("speed", strconv.Itoa(int(p.Speed))),
 55		kv("jump", strconv.Itoa(int(p.Jump))), kv("reaction", strconv.Itoa(int(p.Reaction))))
 56}
 57
 58func kickJSON(k shootout.Kick) string {
 59	return obj(
 60		kv("round", strconv.Itoa(k.Round)),
 61		kv("kicker", q(k.Kicker.String())),
 62		kv("shot", shotJSON(k.Shot.Input())),
 63		kv("mishit", q(penalty.MishitHex(k.Shot.Mishit))),
 64		kv("plan", planJSON(k.Plan)),
 65		kv("seed", q(k.Seed.Hex())),
 66		kv("result", resultJSON(k.Result)),
 67		kv("height", itoa(k.Height)),
 68		kv("time", itoa(k.Time)),
 69	)
 70}
 71
 72func sessionJSON(s shootout.Session) string {
 73	kicks := make([]string, len(s.Kicks))
 74	for i, k := range s.Kicks {
 75		kicks[i] = kickJSON(k)
 76	}
 77	sc := s.Score
 78	return obj(
 79		kv("gameId", strconv.FormatUint(s.GameID, 10)),
 80		kv("no", strconv.Itoa(s.No)),
 81		kv("player", q(string(s.Player))),
 82		kv("status", q(s.Status.String())),
 83		kv("paid", itoa(s.Paid)),
 84		kv("score", obj(kv("playerKicks", strconv.Itoa(sc.PlayerKicks)), kv("playerGoals", strconv.Itoa(sc.PlayerGoals)),
 85			kv("chainKicks", strconv.Itoa(sc.ChainKicks)), kv("chainGoals", strconv.Itoa(sc.ChainGoals)))),
 86		kv("next", q(s.Next())),
 87		kv("kicks", "["+strings.Join(kicks, ",")+"]"),
 88		kv("joinHeight", itoa(s.JoinHeight)),
 89		kv("lastActionHeight", itoa(s.LastActionHeight)),
 90		kv("endHeight", itoa(s.EndHeight)),
 91	)
 92}
 93
 94// ---------------------------------------------------------------- Render
 95
 96// Render shows the games list, one game ("game/<id>") or one shootout
 97// ("game/<id>/<session>").
 98func Render(path string) string {
 99	parts := strings.Split(strings.Trim(path, "/"), "/")
100	if len(parts) >= 2 && parts[0] == "game" {
101		id, err := strconv.ParseUint(parts[1], 10, 64)
102		if err != nil {
103			return "# Not found\n"
104		}
105		if len(parts) >= 3 {
106			no, err := strconv.Atoi(parts[2])
107			if err != nil {
108				return "# Not found\n"
109			}
110			return renderSession(id, no)
111		}
112		return renderGame(id)
113	}
114	return renderHome()
115}
116
117func ugnot(n int64) string {
118	whole := n / 1000000
119	frac := n % 1000000
120	if frac == 0 {
121		return itoa(whole) + " GNOT"
122	}
123	f := strconv.FormatInt(frac+1000000, 10)[1:]
124	return itoa(whole) + "." + strings.TrimRight(f, "0") + " GNOT"
125}
126
127func renderHome() string {
128	var b strings.Builder
129	b.WriteString("# Spot Kick\n\nA pay-to-play penalty shootout against the chain. ")
130	b.WriteString("Join an open game by paying its entry fee, then take five kicks and keep five. ")
131	b.WriteString("Beat the chain and the pot is yours.\n\n")
132	b.WriteString("| Game | Status | Pot | Entry | Challengers |\n|---|---|---:|---:|---:|\n")
133	n := 0
134	games.ReverseIterate("", "", func(_ string, v any) bool {
135		g := v.(*shootout.Game)
136		id := strconv.FormatUint(g.ID, 10)
137		b.WriteString("| [#" + id + "](:game/" + id + ") | " + g.Status.String() + " | " + ugnot(g.Pot) +
138			" | " + ugnot(g.EntryFee) + " | " + strconv.Itoa(g.Sessions) + " |\n")
139		n++
140		return n >= 50
141	})
142	if n == 0 {
143		b.WriteString("| _no games yet_ | | | | |\n")
144	}
145	b.WriteString("\nRules engine: ")
146	if p := proxy.LivePath(); p != "" {
147		b.WriteString("`" + p + "` — " + live().Describe() + "\n")
148	} else {
149		b.WriteString("_none accepted yet_\n")
150	}
151	return b.String()
152}
153
154func renderGame(id uint64) string {
155	g, ok := getGame(id)
156	if !ok {
157		return "# Game not found\n"
158	}
159	sid := strconv.FormatUint(id, 10)
160	var b strings.Builder
161	b.WriteString("# Game #" + sid + "\n\n")
162	b.WriteString("**" + g.Status.String() + "** · pot **" + ugnot(g.Pot) + "** · entry " + ugnot(g.EntryFee) +
163		" · timeout " + itoa(g.TimeoutBlocks) + " blocks\n\n")
164	b.WriteString("Created by `" + string(g.Creator) + "` at height " + itoa(g.CreatedHeight) + ".\n\n")
165	if g.Status == shootout.GameWon {
166		b.WriteString("Won by `" + string(g.Winner) + "` at height " + itoa(g.ClosedHeight) + ".\n\n")
167	}
168	b.WriteString("Constants: `" + g.Config.String() + "`\n\n")
169	if g.Sessions > 0 {
170		b.WriteString("## Challengers\n\n| # | Player | Status | Score |\n|---|---|---|---|\n")
171		for no := g.Sessions; no >= 1 && no > g.Sessions-50; no-- {
172			s, ok := getSession(id, no)
173			if !ok {
174				continue
175			}
176			sn := strconv.Itoa(no)
177			b.WriteString("| [" + sn + "](:game/" + sid + "/" + sn + ") | `" + string(s.Player) + "` | " +
178				s.Status.String() + " | " + score(s.Score) + " |\n")
179		}
180	}
181	return b.String()
182}
183
184func score(s penalty.Shootout) string {
185	return strconv.Itoa(s.PlayerGoals) + "/" + strconv.Itoa(s.PlayerKicks) + " – " +
186		strconv.Itoa(s.ChainGoals) + "/" + strconv.Itoa(s.ChainKicks)
187}
188
189func renderSession(id uint64, no int) string {
190	s, ok := getSession(id, no)
191	if !ok {
192		return "# Shootout not found\n"
193	}
194	var b strings.Builder
195	b.WriteString("# Game #" + strconv.FormatUint(id, 10) + " · challenger " + strconv.Itoa(no) + "\n\n")
196	b.WriteString("`" + string(s.Player) + "` · **" + s.Status.String() + "** · player " + score(s.Score) +
197		" chain · next: " + s.Next() + "\n\n")
198	b.WriteString("| Round | Kicker | Shot | Keeper plan | Outcome | At (mm) |\n|---|---|---|---|---|---|\n")
199	for _, k := range s.Kicks {
200		b.WriteString("| " + strconv.Itoa(k.Round) + " | " + k.Kicker.String() + " | " + k.Shot.Input().String() +
201			" | " + k.Plan.String() + " (" + k.Result.Keeper.String() + ") | **" + k.Result.Outcome.String() +
202			"** | " + itoa(k.Result.X) + ", " + itoa(k.Result.Y) + " |\n")
203	}
204	return b.String()
205}