Skip to content

Commit

Permalink
Merge pull request #16 from iamananya/multi
Browse files Browse the repository at this point in the history
Implementation of multiple gacha system
  • Loading branch information
GuiltyMorishita authored Jun 7, 2023
2 parents 01aa7c9 + 0770127 commit 03a235f
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 100 deletions.
Binary file modified cmd/main/main
Binary file not shown.
2 changes: 1 addition & 1 deletion pkg/config/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var (
)

func Connect() {
d, err := gorm.Open("mysql", "root:1201@/gacharest?charset=utf8&parseTime=True&loc=Local")
d, err := gorm.Open("mysql", "root:1201@/multiple_gacha?charset=utf8&parseTime=True&loc=Local")
if err != nil {
panic(err)
}
Expand Down
170 changes: 104 additions & 66 deletions pkg/controllers/gacha-controller.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package controllers

import (
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"sync"

"github.com/iamananya/ginco-task/pkg/models"
"github.com/iamananya/ginco-task/pkg/utils"
Expand Down Expand Up @@ -125,7 +127,7 @@ func ListCharacters(w http.ResponseWriter, r *http.Request) {
func HandleGachaDraw(w http.ResponseWriter, r *http.Request) {
user, ok := r.Context().Value("user").(models.User)
if !ok {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
http.Error(w, "Internal Server Error to retrieve user", http.StatusInternalServerError)
return
}
fmt.Print(user.Name)
Expand All @@ -136,64 +138,64 @@ func HandleGachaDraw(w http.ResponseWriter, r *http.Request) {
return
}
fmt.Printf("Received request: %+v\n", reqBody)

gacha, err := models.GetGachaByID(reqBody.GachaID)
if err != nil {
http.Error(w, "Invalid gacha ID", http.StatusBadRequest)
return
}

characters, err := models.GetAllCharacters()
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
http.Error(w, "Internal Server Error in characters", http.StatusInternalServerError)
return
}
characterPool := generateCharacterPool(characters)
var characterPointers []*models.Character
for _, character := range characters {
charac := character
characterPointers = append(characterPointers, &charac)
}
characterPool := generateCharacterPool(characterPointers, gacha.RarityR, gacha.RaritySR, gacha.RaritySSR, reqBody.Times)
response := models.GachaDrawResponse{
Results: make([]models.CharacterResponse, reqBody.Times),
}
// Create a slice to store the user characters for batch insert
userCharacters := make([]*models.UserCharacter, reqBody.Times)

// A buffered channel to receive the drawn characters
drawnCharacters := make(chan models.CharacterResponse, reqBody.Times)

// WaitGroup to synchronize goroutines
var wg sync.WaitGroup

// Worker pool with a fixed number of goroutines
numWorkers := 10
tasks := make(chan int, reqBody.Times)

// Start goroutines to process tasks concurrently
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for range tasks {
character, err := models.DrawCharacter(characters, characterPool) // Simulate drawing a character
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
characterResponse := models.CharacterResponse{
CharacterID: fmt.Sprintf("Character-%d", character.ID),
Name: character.Name,
Rarity: character.Rarity,
}
drawnCharacters <- characterResponse
}
}()
}

// Enqueue tasks
for i := 0; i < reqBody.Times; i++ {
tasks <- i
}

// Close the tasks channel and wait for all goroutines to finish
go func() {
close(tasks)
wg.Wait()
close(drawnCharacters)
}()
character, err := drawCharacter(&characterPool)
if err != nil {
http.Error(w, "Internal Server Error in drawing characters", http.StatusInternalServerError)
return
}
userCharacter := models.UserCharacter{
UserID: user.ID,
CharacterID: character.ID,
GachaID: gacha.ID,
AttackPower: character.AttackPower,
Defense: character.Defense,
Speed: character.Speed,
HitPoints: character.HitPoints,
CriticalHitRate: character.CriticalHitRate,
ElementalAffinity: character.ElementalAffinity,
Rarity: character.Rarity,
Synergy: character.Synergy,
Evolution: character.Evolution,
}
// Store the user character in the batch slice
userCharacters[i] = &userCharacter

// Collect the drawn characters from the channel and populate the response
i := 0
for characterResponse := range drawnCharacters {
characterResponse := models.CharacterResponse{
CharacterID: fmt.Sprintf("Character-%d", character.ID),
Name: character.Name,
Rarity: character.Rarity,
}
response.Results[i] = characterResponse
i++
}
// Batch insert user characters into the database
if err := models.CreateUserCharacterBatch(userCharacters); err != nil {
http.Error(w, "Internal Server Error in creating batch", http.StatusInternalServerError)
return
}

respBody, err := json.Marshal(response)
Expand All @@ -207,29 +209,65 @@ func HandleGachaDraw(w http.ResponseWriter, r *http.Request) {
w.Write(respBody)
}

func generateCharacterPool(characters []models.Character) []models.Character {
var characterPool []models.Character
func generateCharacterPool(characters []*models.Character, rarityR, raritySR, raritySSR float64, numDraws int) []*models.Character {
characterPool := make([]*models.Character, 0)

characters_all := make([]string, 0)

rarityProbabilities := map[string]float64{
"SSR": raritySSR,
"SR": raritySR,
"R": rarityR,
}

for _, character := range characters {
rarity := character.Rarity

// Assign the probability based on rarity
var probability int
switch rarity {
case "SSR":
probability = 5
case "SR":
probability = 15
case "R":
probability = 80
draws := rarityProbabilities[rarity]
rarityDraws := int(draws * float64(numDraws))
for i := 0; i < rarityDraws; i++ {
characters_all = append(characters_all, character.Name)
}

// Add the character to the pool multiple times based on its probability
poolSize := probability
for i := 0; i < poolSize; i++ {
characterPool = append(characterPool, character)
}
poolSize := len(characters_all)
for i := 0; i < poolSize; i++ {
randIndex, err := rand.Int(rand.Reader, big.NewInt(int64(poolSize-i)))
if err != nil {
fmt.Printf("Failed to generate random number: %s\n", err.Error())
return characterPool
}
index := int(randIndex.Int64())
characterName := characters_all[index]
character := findCharacterByName(characters, characterName)
characterPool = append(characterPool, character)
// Remove the selected character from the pool
characters_all[index] = characters_all[poolSize-i-1]
characters_all = characters_all[:poolSize-i-1]

}

return characterPool
}
func findCharacterByName(characters []*models.Character, name string) *models.Character {
for _, character := range characters {
if character.Name == name {
return character
}
}
return nil
}
func drawCharacter(characterPool *[]*models.Character) (*models.Character, error) {
poolSize := len(*characterPool)
if poolSize == 0 {
return nil, errors.New("empty character pool")
}
// Randomly select a character from the pool
randIndex, err := rand.Int(rand.Reader, big.NewInt(int64(poolSize)))
if err != nil {
return nil, errors.New("failed to generate random number")
}
index := int(randIndex.Int64())
character := (*characterPool)[index]
// Used the reference of character pool to update the size of the pool
*characterPool = append((*characterPool)[:index], (*characterPool)[index+1:]...)
return character, nil
}
Loading

0 comments on commit 03a235f

Please sign in to comment.