diff --git a/README.md b/README.md index 182cc7c..4a4f99d 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Slots games server. Releases functionality for Megajack, Novomatic, NetEnt, BetS Server provides HTTP-based API for popular slots and have well-optimized performance for thousands requests per second. Can be deployed on dedicated server or as portable application for Linux or Windows. ```text -total: 125 games, 64 algorithms, 9 providers -AGT: 43 games +total: 127 games, 64 algorithms, 9 providers +AGT: 45 games Aristocrat: 4 games BetSoft: 3 games Megajack: 3 games @@ -30,6 +30,8 @@ Slotopol: 4 games *Last added games*: +* '[Book of Set](https://demo.agtsoftware.com/games/agt/bookofset)' AGT 5x3 videoslot +* '[Pharaoh II](https://demo.agtsoftware.com/games/agt/pharaoh2)' AGT 5x3 videoslot * '[Lucky Slot](https://demo.agtsoftware.com/games/agt/luckyslot)' AGT 5x3 videoslot * '[Egypt](https://demo.agtsoftware.com/games/agt/egypt)' AGT 5x3 videoslot * '[Sun City](https://demo.agtsoftware.com/games/agt/suncity)' AGT 5x3 videoslot diff --git a/api/errcodes.go b/api/errcodes.go index 0e7ce78..a128afb 100644 --- a/api/errcodes.go +++ b/api/errcodes.go @@ -102,11 +102,12 @@ const ( // POST /game/rtp/get - AEC_game_rdget_nobind - AEC_game_rdget_notopened - AEC_game_rdget_noclub - AEC_game_rdget_nouser - AEC_game_rdget_noaccess + AEC_game_rtpget_nobind + AEC_game_rtpget_notopened + AEC_game_rtpget_noinfo + AEC_game_rtpget_noclub + AEC_game_rtpget_nouser + AEC_game_rtpget_noaccess // POST /slot/bet/get diff --git a/api/game.go b/api/game.go index 5643adf..cfdce03 100644 --- a/api/game.go +++ b/api/game.go @@ -95,9 +95,7 @@ func ApiGameJoin(c *gin.Context) { ret.GID = gid ret.Game = anygame // make game screen object - club.mux.RLock() var rtp = GetRTP(user, club) - club.mux.RUnlock() switch game := anygame.(type) { case slot.SlotGame: var scrn = game.NewScreen() @@ -230,40 +228,46 @@ func ApiGameRtpGet(c *gin.Context) { var ret struct { XMLName xml.Name `json:"-" yaml:"-" xml:"ret"` MRTP float64 `json:"mrtp" yaml:"mrtp" xml:"mrtp"` + RTP float64 `json:"rtp" yaml:"rtp" xml:"rtp"` } if err = c.ShouldBind(&arg); err != nil { - Ret400(c, AEC_game_rdget_nobind, err) + Ret400(c, AEC_game_rtpget_nobind, err) return } var scene *Scene if scene, ok = Scenes.Get(arg.GID); !ok { - Ret404(c, AEC_game_rdget_notopened, ErrNotOpened) + Ret404(c, AEC_game_rtpget_notopened, ErrNotOpened) + return + } + + var gi *game.GameInfo + if gi = game.GetInfo(scene.Alias); gi == nil { + Ret500(c, AEC_game_rtpget_noinfo, ErrNoAliase) return } var club *Club if club, ok = Clubs.Get(scene.CID); !ok { - Ret500(c, AEC_game_rdget_noclub, ErrNoClub) + Ret500(c, AEC_game_rtpget_noclub, ErrNoClub) return } var user *User if user, ok = Users.Get(scene.UID); !ok { - Ret500(c, AEC_game_rdget_nouser, ErrNoUser) + Ret500(c, AEC_game_rtpget_nouser, ErrNoUser) return } var admin, al = MustAdmin(c, scene.CID) if admin != user && al&ALgame == 0 { - Ret403(c, AEC_game_rdget_noaccess, ErrNoAccess) + Ret403(c, AEC_game_rtpget_noaccess, ErrNoAccess) return } - club.mux.RLock() ret.MRTP = GetRTP(user, club) - club.mux.RUnlock() + ret.RTP = gi.FindRTP(ret.MRTP) RetOk(c, ret) } diff --git a/api/keno.go b/api/keno.go index 2147185..941957d 100644 --- a/api/keno.go +++ b/api/keno.go @@ -322,8 +322,8 @@ func ApiKenoSpin(c *gin.Context) { club.mux.RLock() var bank = club.Bank - var mrtp = GetRTP(user, club) club.mux.RUnlock() + var mrtp = GetRTP(user, club) // spin until gain less than bank value var wins keno.Wins diff --git a/api/logic.go b/api/logic.go index 1fc2d90..0c968bb 100644 --- a/api/logic.go +++ b/api/logic.go @@ -209,6 +209,8 @@ func GetRTP(user *User, club *Club) float64 { return props.MRTP } } + club.mux.RLock() + defer club.mux.RUnlock() if club.MRTP != 0 { return club.MRTP } diff --git a/api/props.go b/api/props.go index f9673f9..2104c69 100644 --- a/api/props.go +++ b/api/props.go @@ -316,9 +316,7 @@ func ApiPropsRtpGet(c *gin.Context) { } if arg.All { - club.mux.RLock() ret.MRTP = GetRTP(user, club) - club.mux.RUnlock() } else { ret.MRTP = user.GetRTP(arg.CID) } diff --git a/api/slot.go b/api/slot.go index 2376f08..0cc3dfc 100644 --- a/api/slot.go +++ b/api/slot.go @@ -286,8 +286,8 @@ func ApiSlotSpin(c *gin.Context) { club.mux.RLock() var bank = club.Bank - var mrtp = GetRTP(user, club) club.mux.RUnlock() + var mrtp = GetRTP(user, club) // spin until gain less than bank value var wins slot.Wins @@ -436,8 +436,8 @@ func ApiSlotDoubleup(c *gin.Context) { club.mux.RLock() var bank = club.Bank - var mrtp = GetRTP(user, club) club.mux.RUnlock() + var mrtp = GetRTP(user, club) var multgain float64 // new multiplied gain if bank >= risk*float64(arg.Mult) { diff --git a/docs/list-all.md b/docs/list-all.md index 0721235..ea16ec2 100644 --- a/docs/list-all.md +++ b/docs/list-all.md @@ -27,6 +27,7 @@ 'Big Five' AGT 5x3 videoslot 'Bigfoot' AGT 5x3 videoslot 'Bitcoin' AGT 5x3 videoslot +'Book of Set' AGT 5x3 videoslot 'Captain's Treasure' Playtech 5x3 videoslot 'Casino' AGT 5x4 videoslot 'Champagne' Megajack 5x3 videoslot @@ -84,8 +85,9 @@ 'Marco Polo' Novomatic 5x3 videoslot 'Pandora's Box' NetEnt 5x3 videoslot 'Panther Moon' Playtech 5x3 videoslot -'Pharaon's Gold II' Novomatic 5x3 videoslot -'Pharaon's Gold III' Novomatic 5x3 videoslot +'Pharaoh II' AGT 5x3 videoslot +'Pharaoh's Gold II' Novomatic 5x3 videoslot +'Pharaoh's Gold III' Novomatic 5x3 videoslot 'Piggy Riches' NetEnt 5x3 videoslot 'Pirates Gold' AGT 5x3 videoslot 'Plenty on Twenty' Novomatic 5x3 videoslot @@ -128,8 +130,8 @@ 'Wild Witches' NetEnt 5x3 videoslot 'Wizard' AGT 5x4 videoslot -total: 125 games, 64 algorithms, 9 providers -AGT: 43 games +total: 127 games, 64 algorithms, 9 providers +AGT: 45 games Aristocrat: 4 games BetSoft: 3 games Megajack: 3 games diff --git a/game/linkdata.go b/game/linkdata.go index 209a796..6b700dd 100644 --- a/game/linkdata.go +++ b/game/linkdata.go @@ -2,6 +2,7 @@ package game import ( "context" + "math" "sort" "github.com/slotopol/server/util" @@ -58,6 +59,17 @@ func MakeRtpList[T any](reelsmap map[float64]T) []float64 { return list } +func GetInfo(alias string) *GameInfo { + for _, gi := range GameList { + for _, ga := range gi.Aliases { + if aid := util.ToID(ga.Prov + "/" + ga.Name); alias == aid { + return gi + } + } + } + return nil +} + func (gi *GameInfo) SetupFactory(game func() any, scan Scanner) { GameList = append(GameList, gi) for _, ga := range gi.Aliases { @@ -66,3 +78,12 @@ func (gi *GameInfo) SetupFactory(game func() any, scan Scanner) { ScanFactory[aid] = scan } } + +func (gi *GameInfo) FindRTP(mrtp float64) (rtp float64) { + for _, p := range gi.RTP { + if math.Abs(mrtp-p) < math.Abs(mrtp-rtp) { + rtp = p + } + } + return +} diff --git a/game/slot/agt/aislot/aislot_link.go b/game/slot/agt/aislot/aislot_link.go index 0a5eaa6..9ea5f1a 100644 --- a/game/slot/agt/aislot/aislot_link.go +++ b/game/slot/agt/aislot/aislot_link.go @@ -8,8 +8,10 @@ import ( var Info = game.GameInfo{ Aliases: []game.GameAlias{ - {Prov: "AGT", Name: "AI"}, // see: https://demo.agtsoftware.com/games/agt/aislot - {Prov: "AGT", Name: "Tesla"}, // see: https://demo.agtsoftware.com/games/agt/tesla + {Prov: "AGT", Name: "AI"}, // see: https://demo.agtsoftware.com/games/agt/aislot + {Prov: "AGT", Name: "Tesla"}, // see: https://demo.agtsoftware.com/games/agt/tesla + {Prov: "AGT", Name: "Book of Set"}, // see: https://demo.agtsoftware.com/games/agt/bookofset + {Prov: "AGT", Name: "Pharaoh II"}, // see: https://demo.agtsoftware.com/games/agt/pharaoh2 }, GP: game.GPsel | game.GPretrig | diff --git a/game/slot/agt/aislot/aislot_rule.go b/game/slot/agt/aislot/aislot_rule.go index 65881ab..eea7312 100644 --- a/game/slot/agt/aislot/aislot_rule.go +++ b/game/slot/agt/aislot/aislot_rule.go @@ -24,7 +24,7 @@ var LinePay = [10][5]float64{ var ScatPay = [5]float64{0, 0, 2, 25, 250} // 1 scatter // Bet lines -var BetLines = slot.BetLinesAgt5x3[:15] +var BetLines = slot.BetLinesAgt5x3[:30] type Game struct { slot.Slot5x3 `yaml:",inline"` diff --git a/game/slot/novomatic/dolphinspearl/dolphinspearl_link.go b/game/slot/novomatic/dolphinspearl/dolphinspearl_link.go index 9cfa59d..74169d3 100644 --- a/game/slot/novomatic/dolphinspearl/dolphinspearl_link.go +++ b/game/slot/novomatic/dolphinspearl/dolphinspearl_link.go @@ -19,8 +19,8 @@ var Info = game.GameInfo{ {Prov: "Novomatic", Name: "King Of Cards"}, {Prov: "Novomatic", Name: "Lucky Lady's Charm"}, {Prov: "Novomatic", Name: "Lucky Lady's Charm Deluxe"}, - {Prov: "Novomatic", Name: "Pharaon's Gold II"}, - {Prov: "Novomatic", Name: "Pharaon's Gold III"}, + {Prov: "Novomatic", Name: "Pharaoh's Gold II"}, + {Prov: "Novomatic", Name: "Pharaoh's Gold III"}, {Prov: "Novomatic", Name: "Polar Fox"}, {Prov: "Novomatic", Name: "Ramses II"}, {Prov: "Novomatic", Name: "Royal Treasures"}, diff --git a/game/slot/screen.go b/game/slot/screen.go index 57625ab..f7ebb50 100644 --- a/game/slot/screen.go +++ b/game/slot/screen.go @@ -32,6 +32,9 @@ type Screenx struct { data [40]Sym } +// Declare conformity with Screen interface. +var _ Screen = (*Screenx)(nil) + var poolsx = sync.Pool{ New: func() any { return &Screen3x3{} @@ -220,6 +223,9 @@ func (s *Screenx) UnmarshalJSON(b []byte) (err error) { // Screen for 3x3 slots. type Screen3x3 [3][3]Sym +// Declare conformity with Screen interface. +var _ Screen = (*Screen3x3)(nil) + var pools3x3 = sync.Pool{ New: func() any { return &Screen3x3{} @@ -369,6 +375,9 @@ func (s *Screen3x3) FillSym() Sym { // Screen for 5x3 slots. type Screen5x3 [5][3]Sym +// Declare conformity with Screen interface. +var _ Screen = (*Screen5x3)(nil) + var pools5x3 = sync.Pool{ New: func() any { return &Screen5x3{} @@ -515,6 +524,9 @@ func (s *Screen5x3) FillSym() Sym { // Screen for 5x4 slots. type Screen5x4 [5][4]Sym +// Declare conformity with Screen interface. +var _ Screen = (*Screen5x4)(nil) + var pools5x4 = sync.Pool{ New: func() any { return &Screen5x4{}