feat: auto cash-out targets, 1% house edge, terminal aesthetic
Auto cash-out closes a position at exactly the chosen target rather than the next tick's multiplier, and fires whenever the target is at or below the crash point. This is the feature that makes the game playable over a network, where manual timing is at the mercy of latency. House edge drops from 2% to 1% across crash and scratch. Scratch prize tables retuned so the published 99% RTP is exact. Adds docs/API.md: the client uses no private endpoints, so anyone can write a bot against the same API. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/coder/websocket"
|
||||
"github.com/coder/websocket/wsjson"
|
||||
"github.com/drjones/quantum-arcade/pkg/fair"
|
||||
"github.com/drjones/quantum-arcade/pkg/fixed"
|
||||
"github.com/drjones/quantum-arcade/pkg/identity"
|
||||
"github.com/drjones/quantum-arcade/pkg/ledger"
|
||||
"github.com/drjones/quantum-arcade/pkg/room"
|
||||
@@ -31,6 +32,10 @@ import (
|
||||
//go:embed static
|
||||
var staticFiles embed.FS
|
||||
|
||||
// maxAutoCashOut bounds the target a client may request, keeping the
|
||||
// conversion to fixed-point well inside the representable range.
|
||||
const maxAutoCashOut = 1_000_000
|
||||
|
||||
// Games offered as shared rounds. They share one engine and differ in how the
|
||||
// client renders the climb.
|
||||
var games = []string{"rocket", "orbital", "tower"}
|
||||
@@ -361,6 +366,9 @@ func (s *server) handleBet(w http.ResponseWriter, r *http.Request) {
|
||||
Game string `json:"game"`
|
||||
StakeMsat int64 `json:"stake_msat"`
|
||||
Nickname string `json:"nickname"`
|
||||
// AutoCashOut is an optional target multiplier, e.g. 2.5 for 2.50x.
|
||||
// Zero or absent means no target.
|
||||
AutoCashOut float64 `json:"auto_cashout"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, http.StatusBadRequest, "malformed request")
|
||||
@@ -371,7 +379,18 @@ func (s *server) handleBet(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, http.StatusNotFound, "no such game")
|
||||
return
|
||||
}
|
||||
if err := rm.PlaceBet(r.Context(), id, pk, req.Nickname, req.StakeMsat); err != nil {
|
||||
// Convert the target to fixed-point at the boundary; everything past this
|
||||
// point is integer arithmetic.
|
||||
var target fixed.F
|
||||
if req.AutoCashOut > 0 {
|
||||
if req.AutoCashOut > float64(maxAutoCashOut) {
|
||||
writeErr(w, http.StatusBadRequest, "auto cash-out target is too large")
|
||||
return
|
||||
}
|
||||
target = fixed.F(req.AutoCashOut * float64(fixed.One))
|
||||
}
|
||||
|
||||
if err := rm.PlaceBet(r.Context(), id, pk, req.Nickname, req.StakeMsat, target); err != nil {
|
||||
writeErr(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user