420 lines
9.6 KiB
Go
420 lines
9.6 KiB
Go
package sugarrush
|
||
|
||
import (
|
||
"github.com/tomas-qstarrs/boost/mathx"
|
||
"github.com/tomas-qstarrs/boost/randx"
|
||
"mongo.games.com/game/gamesrv/slotspkg/internal/generic/key"
|
||
"mongo.games.com/game/gamesrv/slotspkg/internal/module/shared"
|
||
"mongo.games.com/game/gamesrv/slotspkg/slots/intf"
|
||
"mongo.games.com/game/gamesrv/slotspkg/slots/plugin/generic"
|
||
)
|
||
|
||
// PluginBaseSpin is derived from generic.PluginBase
|
||
type PluginBaseSpin struct {
|
||
generic.PluginBase
|
||
}
|
||
|
||
// Theme implements generic.PluginBase.Theme
|
||
func (p *PluginBaseSpin) Theme() string {
|
||
return key.SugarRush
|
||
}
|
||
|
||
type CustomEliminate struct {
|
||
LinkPositions []*shared.LinkPositions
|
||
AppendSymbols [][]int64
|
||
FormattedSymbols [][]int64
|
||
LinePays []float64
|
||
LineMultis []int64
|
||
WinCoins []int64
|
||
MaxWin bool
|
||
}
|
||
|
||
type CustomPay struct {
|
||
Pay int64
|
||
}
|
||
|
||
type CustomMulti struct {
|
||
MultiTable [][]int64
|
||
}
|
||
|
||
type Col struct {
|
||
Values []int64
|
||
}
|
||
|
||
type Formation struct {
|
||
Cols []Col
|
||
}
|
||
|
||
// Customs implements generic.PluginBase.Customs
|
||
func (p *PluginBaseSpin) Customs() []interface{} {
|
||
return []interface{}{
|
||
&CustomPay{},
|
||
&CustomEliminate{},
|
||
&MultiTable{},
|
||
&CustomMulti{},
|
||
}
|
||
}
|
||
|
||
// AfterBaseSpin is called after base spin
|
||
func (p *PluginBaseSpin) AfterBaseSpin(m intf.Master) {
|
||
|
||
}
|
||
|
||
func (f *Formation) AddFreeSpin(m intf.Master) {
|
||
if m.Cursor().GetType() != key.BaseSpin {
|
||
return
|
||
}
|
||
scatterCount := Descx(m).RandScatterCount()
|
||
// get current count
|
||
currentCount := int64(0)
|
||
for _, col := range f.Cols {
|
||
for _, v := range col.Values {
|
||
if v == SymbolScatter {
|
||
currentCount++
|
||
}
|
||
}
|
||
}
|
||
|
||
// get col not has scatter
|
||
colNotHasScatter := make([]int64, 0)
|
||
for i, col := range f.Cols {
|
||
hasScatter := false
|
||
for _, v := range col.Values {
|
||
if v == SymbolScatter {
|
||
hasScatter = true
|
||
break
|
||
}
|
||
}
|
||
if !hasScatter {
|
||
colNotHasScatter = append(colNotHasScatter, int64(i))
|
||
}
|
||
}
|
||
|
||
needCount := scatterCount - currentCount
|
||
if needCount <= 0 {
|
||
needCount = 0
|
||
}
|
||
|
||
// rand new scatter for col not has scatter
|
||
randx.RandShuffle(m.Randx(), colNotHasScatter)
|
||
for i := int64(0); i < needCount; i++ {
|
||
col := colNotHasScatter[i]
|
||
f.Cols[col].Values[randx.RandRangeInt63n(m.Randx(), 0, 6)] = SymbolScatter
|
||
}
|
||
}
|
||
|
||
func (f *Formation) fillCol(m intf.Master, col int64, eliminate bool, isFree bool) []int64 {
|
||
appendValues := make([]int64, 0)
|
||
values := f.Cols[col].Values
|
||
left := ReelHeight - int64(len(values))
|
||
scatterCount := int64(0)
|
||
for _, v := range values {
|
||
if v == SymbolScatter {
|
||
scatterCount++
|
||
break
|
||
}
|
||
}
|
||
|
||
topItemID := int64(0)
|
||
isTop := false
|
||
if len(values) > 0 && eliminate {
|
||
topItemID = values[0]
|
||
isTop = true
|
||
}
|
||
|
||
for left > 0 {
|
||
itemID := Descx(m).RandItemID(topItemID, isFree, isTop)
|
||
if isTop {
|
||
isTop = false
|
||
}
|
||
topItemID = itemID
|
||
|
||
itemCount := Descx(m).RandItemCount(itemID, left, isFree)
|
||
if SymbolScatter == itemID {
|
||
itemCount = mathx.Min(itemCount, Descx(m).GetOtherConfig().MaxScatterPerCol-scatterCount)
|
||
}
|
||
|
||
if SymbolScatter == itemID {
|
||
scatterCount += itemCount
|
||
}
|
||
|
||
for i := int64(0); i < itemCount; i++ {
|
||
// append at head
|
||
values = append([]int64{itemID}, values...)
|
||
appendValues = append([]int64{itemID}, appendValues...)
|
||
}
|
||
|
||
left -= itemCount
|
||
}
|
||
f.Cols[col].Values = values
|
||
return appendValues
|
||
}
|
||
|
||
func (f *Formation) fillCols(m intf.Master, eliminate bool, isFree bool) [][]int64 {
|
||
newSymbols := make([][]int64, ReelWidth)
|
||
for j := int64(0); j < ReelWidth; j++ {
|
||
newSymbols[j] = f.fillCol(m, j, eliminate, isFree)
|
||
}
|
||
return newSymbols
|
||
}
|
||
|
||
func (f *Formation) GetValues() [][]int64 {
|
||
values := make([][]int64, ReelHeight)
|
||
for j := int64(0); j < ReelWidth; j++ {
|
||
for i := int64(0); i < ReelHeight; i++ {
|
||
values[j] = append(values[j], f.Cols[j].Values[i])
|
||
}
|
||
}
|
||
return values
|
||
}
|
||
|
||
func createFormation() *Formation {
|
||
formation := &Formation{}
|
||
for i := 0; i < 7; i++ {
|
||
formation.Cols = make([]Col, 7)
|
||
}
|
||
return formation
|
||
}
|
||
|
||
func (p *PluginBaseSpin) RandFormation(m intf.Master) [][]int64 {
|
||
formation := &Formation{
|
||
Cols: make([]Col, 7),
|
||
}
|
||
for i := 0; i < 7; i++ {
|
||
formation.Cols[i] = Col{
|
||
Values: make([]int64, 0),
|
||
}
|
||
formation.fillCol(m, int64(i), false, m.Cursor().GetType() == key.FreeSpin)
|
||
}
|
||
|
||
// 购买freespin
|
||
if m.Exists(key.MachineRoundType) && m.Value(key.MachineRoundType).(int64) == RoundTypeBuyFreeSpin {
|
||
formation.AddFreeSpin(m)
|
||
}
|
||
|
||
return formation.GetValues()
|
||
}
|
||
|
||
func DumpReel(symbols [][]int64) {
|
||
// str := ""
|
||
// for row := int64(0); row < 7; row++ {
|
||
// for col := int64(0); col < 7; col++ {
|
||
// str += fmt.Sprintf("%d ", symbols[col][row])
|
||
// }
|
||
// str += "\n"
|
||
// }
|
||
// log.Infof("reel: \n%s\n", str)
|
||
}
|
||
|
||
func IsPositionLinked(linkPositions []*shared.LinkPositions, row int64, col int64) bool {
|
||
for _, positions := range linkPositions {
|
||
for _, position := range positions.Positions {
|
||
if position == col*7+row {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
type MultiTable struct {
|
||
Multi [][]int64
|
||
Count [][]int64
|
||
}
|
||
|
||
func (t *MultiTable) Clear() {
|
||
for i := 0; i < 7; i++ {
|
||
for j := 0; j < 7; j++ {
|
||
t.Multi[i][j] = 0
|
||
t.Count[i][j] = 0
|
||
}
|
||
}
|
||
}
|
||
|
||
func (t *MultiTable) Update(m intf.Master, linkPositions []*shared.LinkPositions) {
|
||
for _, positions := range linkPositions {
|
||
for _, position := range positions.Positions {
|
||
t.Count[position/7][position%7]++
|
||
}
|
||
}
|
||
// update multi
|
||
for i := 0; i < 7; i++ {
|
||
for j := 0; j < 7; j++ {
|
||
t.Multi[i][j] = Descx(m).GetMulti(t.Count[i][j])
|
||
}
|
||
}
|
||
}
|
||
|
||
func calcMulti(linkPositions *shared.LinkPositions, multiTable *MultiTable) int64 {
|
||
totalMulti := int64(0)
|
||
|
||
for _, p := range linkPositions.Positions {
|
||
if multiTable.Multi[p/7][p%7] > 1 {
|
||
totalMulti += multiTable.Multi[p/7][p%7]
|
||
}
|
||
}
|
||
|
||
if totalMulti < 1 {
|
||
totalMulti = 1
|
||
}
|
||
|
||
return totalMulti
|
||
}
|
||
|
||
func (p *PluginBaseSpin) Eliminate(m intf.Master, customPay *CustomPay, multiTable *MultiTable) bool {
|
||
cursorFormation := m.CursorFormation()
|
||
formattedSymbols := cursorFormation.GetReelFormattedSymbols()
|
||
DumpReel(formattedSymbols)
|
||
// 清空基础赢钱
|
||
cursorFormation.SetWin(0)
|
||
|
||
// 根据赔付计算multi type
|
||
linkPositions, _, linePays := m.TryLinkMatrixSymbols(1, formattedSymbols)
|
||
DumpReel(multiTable.Multi)
|
||
lineMultis := make([]int64, len(linePays))
|
||
|
||
symbols := cursorFormation.GetReelFormattedSymbols()
|
||
success := mathx.Sum(linePays) > 0
|
||
|
||
// erase
|
||
formation := createFormation()
|
||
for j := int64(0); j < 7; j++ {
|
||
for i := int64(0); i < 7; i++ {
|
||
if !IsPositionLinked(linkPositions, i, j) {
|
||
formation.Cols[j].Values = append(formation.Cols[j].Values, symbols[j][i])
|
||
}
|
||
}
|
||
}
|
||
|
||
appendFormattedSymbols := formation.fillCols(m, true, m.Cursor().GetType() == key.FreeSpin)
|
||
|
||
lineNum := len(linePays)
|
||
winCoins := make([]int64, lineNum)
|
||
totalPay := int64(0)
|
||
|
||
for lineIdx, pay := range linePays {
|
||
multi := calcMulti(linkPositions[lineIdx], multiTable)
|
||
lineMultis[lineIdx] = multi
|
||
winCoins[lineIdx] = int64(float64(m.Cursor().GetSingleBet()) * pay * float64(multi))
|
||
totalPay += winCoins[lineIdx]
|
||
}
|
||
|
||
isMaxWin := false
|
||
|
||
maxValue := Descx(m).GetOtherConfig().FreespinMaxWin * m.Cursor().GetBet()
|
||
currentWin := m.TotalWin() + customPay.Pay
|
||
|
||
// log.Infof("Eliminate totalPay: %d, currentWin: %d, maxValue: %d", totalPay, currentWin, maxValue)
|
||
|
||
if m.Cursor().GetType() == key.FreeSpin {
|
||
if currentWin+totalPay >= maxValue {
|
||
success = false
|
||
isMaxWin = true
|
||
totalPay = maxValue - currentWin
|
||
m.SetProgressLeft(0)
|
||
}
|
||
}
|
||
|
||
// 添加后续feature,这里是消除
|
||
m.AddCursorFeature(&CustomEliminate{
|
||
LinkPositions: linkPositions,
|
||
AppendSymbols: appendFormattedSymbols,
|
||
FormattedSymbols: formattedSymbols,
|
||
LinePays: linePays,
|
||
LineMultis: lineMultis,
|
||
WinCoins: winCoins,
|
||
MaxWin: isMaxWin,
|
||
}).SetLifetime(1)
|
||
|
||
DumpReel(formation.GetValues())
|
||
cursorFormation.SetFormattedSymbols(formation.GetValues())
|
||
|
||
// 累加pay
|
||
customPay.Pay += totalPay
|
||
multiTable.Update(m, linkPositions)
|
||
|
||
// add new
|
||
return success
|
||
}
|
||
|
||
func (p *PluginBaseSpin) BeforeSpin(m intf.Master) {
|
||
m.AddCursorFeature(&CustomPay{}).SetLifetime(1)
|
||
}
|
||
|
||
func (p *PluginBaseSpin) Spin(m intf.Master, isFree bool) {
|
||
// 生成轴
|
||
symbols := p.RandFormation(m)
|
||
DumpReel(symbols)
|
||
formation := m.CursorFormation()
|
||
formation.SetFormattedSymbols(symbols)
|
||
customPay := m.CursorCustom(&CustomPay{}).(*CustomPay)
|
||
|
||
table := getMultiTable(m)
|
||
|
||
// 存储 Formation元素
|
||
formation.SetFormattedDisplaySymbols(symbols)
|
||
|
||
// 消除
|
||
n := 0
|
||
for p.Eliminate(m, customPay, table) {
|
||
n++
|
||
if n > 1000 {
|
||
break
|
||
}
|
||
}
|
||
|
||
if customPay.Pay > 0 {
|
||
m.CursorFeature(&CustomPay{}).SetWin(customPay.Pay)
|
||
}
|
||
|
||
m.AddCursorFeature(&CustomMulti{
|
||
MultiTable: table.Multi,
|
||
}).SetLifetime(1)
|
||
|
||
formattedSymbols := formation.GetReelFormattedSymbols()
|
||
formation.SetFormattedFinalSymbols(formattedSymbols)
|
||
}
|
||
|
||
func (p *PluginBaseSpin) CheckFreeLimit(m intf.Master) {
|
||
|
||
}
|
||
|
||
// AfterSpin implements generic.PluginBase.AfterSpin
|
||
func (p *PluginBaseSpin) AfterSpin(m intf.Master) {
|
||
p.Spin(m, m.Cursor().GetType() == key.FreeSpin)
|
||
|
||
switch m.Cursor().GetType() {
|
||
case key.BaseSpin:
|
||
p.AfterBaseSpin(m)
|
||
case key.FreeSpin:
|
||
p.AfterFreeSpin(m)
|
||
p.CheckFreeLimit(m)
|
||
}
|
||
}
|
||
|
||
// AfterFreeSpin is called after free spin
|
||
func (p *PluginBaseSpin) AfterFreeSpin(m intf.Master) {
|
||
|
||
}
|
||
|
||
func (p *PluginBaseSpin) OnLeaveNode(m intf.Master) {
|
||
if m.Next().GetType() == key.FreeSpin || m.Next().GetType() == key.BaseSpin {
|
||
getMultiTable(m).Clear()
|
||
}
|
||
}
|
||
|
||
func getMultiTable(m intf.Master) *MultiTable {
|
||
if len(m.RootFeatures(&MultiTable{})) == 0 {
|
||
table := &MultiTable{
|
||
Multi: make([][]int64, 7),
|
||
Count: make([][]int64, 7),
|
||
}
|
||
for i := 0; i < 7; i++ {
|
||
table.Multi[i] = make([]int64, 7)
|
||
table.Count[i] = make([]int64, 7)
|
||
}
|
||
m.AddRootFeature(table)
|
||
}
|
||
return m.RootCustom(&MultiTable{}).(*MultiTable)
|
||
}
|