Skip to main content
replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

In my previous questionquestion I asked how could the Redis client be improved. Here I will ask how can the Game data Client can be improved. Game Data client sits on top of Redis Client and uses datatypes from datape package

In my previous question I asked how could the Redis client be improved. Here I will ask how can the Game data Client can be improved. Game Data client sits on top of Redis Client and uses datatypes from datape package

In my previous question I asked how could the Redis client be improved. Here I will ask how can the Game data Client can be improved. Game Data client sits on top of Redis Client and uses datatypes from datape package

Source Link
Vivek
  • 221
  • 1
  • 2
  • 6

Game Data Client: Go based Game server

In my previous question I asked how could the Redis client be improved. Here I will ask how can the Game data Client can be improved. Game Data client sits on top of Redis Client and uses datatypes from datape package

Here is the code:

package gamedatamanager


import (
    "log"
    "reflect"
    "encoding/json"
    "sort"

    "gogameserver/util"
    dt  "gogameserver/datatypes" 
    rcl "gogameserver/redisclient" 
)

const REDIS_NIL string  = "redis: nil"

type GameManager struct {
    rc *rcl.RedisClient
}

func New() (gm * GameManager) {
    return &GameManager{
        rc: rcl.New(),
    }
}

func (gm * GameManager) DelKey(key string) (int64, bool) {
    redisRet, err := gm.rc.DelKey(key)
    if err != nil && err.Error() != REDIS_NIL {
        go log.Printf("\nERROR:DelKey| Key: %s not found, err:%v", key, err)
        return -1, false
    } else {
        return redisRet, true
    }
}

func (gm * GameManager) GetPlayerData(gameName string, playerId string) (string, bool) {
    gameName = gameName+playerId
    playerDataStr, err := gm.rc.GetVal(gameName)
    if err != nil && err.Error() == REDIS_NIL {
        go log.Printf("\nERROR:GetPlayerData: Game %s, playerId %s not found, err:%v", gameName, playerId, err)
        return "player not found", false
    } else {
        return playerDataStr, true
    }
}

func (gm * GameManager) DelPlayerData(gameName string, playerId string) (int64, bool) {
    gm.DelPlayerName(gameName, playerId)
    gameName = gameName+playerId
    redisRet, err := gm.rc.DelKey(gameName)
    if err != nil && err.Error() != REDIS_NIL {
        go log.Printf("\nERROR:DelPlayerData: Game %s, playerId %s not found, err:%v", gameName, playerId, err)
        return -1, false
    } else {
        return redisRet, true
    }
}

// store player data
func (gm * GameManager) StorePlayerData(gameName string, playerData dt.PlayerData) (bool){
    gm.StorePlayerName(gameName, playerData.N, playerData.I)
    gameName = gameName+playerData.I
    err := gm.rc.SaveKeyValForever(gameName, dt.Str(playerData))
    if err != nil && err.Error() != REDIS_NIL {
        go log.Printf("\nERROR:StorePlayerData: Game %s, playerData %v, err:%v", gameName, playerData, err)
        return false
    } else {
        go log.Printf("\nInfo: Success StorePlayerData: Game %s, playerData %v", gameName, playerData)
        return true
    }
}

// store player new score
func (gm * GameManager) StorePlayerScore(gameName string,  score float64, playerId string) (bool){
    currHiScore, err := gm.GetPlayerHighScore(gameName, playerId)
    
    if err != nil && err.Error() != REDIS_NIL {
        go log.Printf("ERROR:StorePlayerScore: Game %s, playerId %s not found. err:'%s'", gameName, playerId, err.Error() )
        return false
    } else {
        hiScoretoday, _ := gm.GetPlayerScoreOnDay(gameName,  playerId , 0) 
        if hiScoretoday < score {
            gm.StorePlayerScoreDaily(gameName , score , playerId)
        }
        if currHiScore < score {
            pDataStr, found := gm.GetPlayerData(gameName, playerId)
            if !found {
                return false
            }
            pData := dt.JsonFromStr(pDataStr)
            pData.A = score 
            // a go routine to update 
            gm.StorePlayerData(gameName, pData)
            redisRet, redisErr := gm.rc.AddToSet(gameName, score, playerId)
            if redisErr != nil && redisErr.Error() != REDIS_NIL {
                go log.Printf("Error:AddToSet: SUCESS gameName:%s, score:%f, playerId:%s, redisErr:%v", gameName, score, playerId, redisErr)
                return false
            }   
            go log.Printf("Info:StorePlayerScore: SUCESS currHiScore:%.6f, newScore:%.6f, Game %s, playerId %s, retcode:%d", currHiScore, score, gameName, playerId, redisRet)
        } else {
            go log.Printf("Info:StorePlayerScore: Already high currHiScore:%.6f, newScore:%.6f, Game %s, playerId %s", currHiScore, score, gameName, playerId)
        }
        return true
    }
}

func getKeyForName(gameName string, playerId string)  string {
    return gameName+playerId+"Name"
}

// store player new score
func (gm * GameManager) StorePlayerName(gameName string,  playerName string, playerId string) (bool){
    key := getKeyForName(gameName, playerId)
    err := gm.rc.SaveKeyValForever(key, playerName)
    if err != nil && err.Error() != REDIS_NIL {
        go log.Printf("\nERROR:StorePlayerName: Game %s, playerName %s, err:%v", gameName, playerName, err)
        return false
    } else {
        go log.Printf("\nInfo: Success StorePlayerName: Game %s, playerName %s", gameName, playerName)
        return true
    }
}

// store player new score
func (gm * GameManager) GetPlayerName(gameName string, playerId string) (string, bool){
    key := getKeyForName(gameName, playerId)
    playerName, err := gm.rc.GetVal(key)
    if err != nil && err.Error() == REDIS_NIL {
        go log.Printf("\nERROR:GetPlayerName: Game %s, playerId %s not found, err:%v", gameName, playerId, err)
        return "playerName not found", false
    } else {
        return playerName, true
    }
}

// store player new score
func (gm * GameManager) DelPlayerName(gameName string,   playerId string) (int64, bool){
    key := getKeyForName(gameName, playerId)
    redisRet, err := gm.rc.DelKey(key)
    if err != nil && err.Error() != REDIS_NIL {
        go log.Printf("\nERROR:DelPlayerName: Game %s, playerId %s not found, err:%v", gameName, playerId, err)
        return -1, false
    } else {
        return redisRet, true
    }
}

// delete player score
func (gm * GameManager) DeletePlayerScore(gameName string,  playerId string) (bool){
    redisRet, redisErr := gm.rc.RemScore(gameName, playerId)
    if redisErr != nil && redisErr.Error() != REDIS_NIL {
        go log.Printf("Error:DeletePlayerScore: SUCESS gameName:%s, playerId:%s, redisErr:%v", gameName, playerId, redisErr)
        return false
    } else {
        go log.Printf("Info :DeletePlayerScore: SUCESS gameName:%s,  playerId:%s, redisRet:%d", gameName, playerId, redisRet)
    }
    return true
}

// get player rank
func (gm * GameManager) GetPlayerRank(gameName string, playerId string) (int64) {
    rank, err := gm.rc.GetRank(gameName, playerId)
    if err != nil && err.Error() != REDIS_NIL {
        go log.Printf("Error:GetPlayerRank: Game %s, playerId %s", gameName, playerId)
        return -1;
    } else {
        return rank+1;
    }
}

// get top x
func (gm * GameManager) GetTopPlayers(gameName string, topCount int64) (string) {
    playeScores := make([]dt.PlayerScore, 0)

    topResults, err := gm.rc.GetTop(gameName, topCount)
    if err != nil && err.Error() != REDIS_NIL{
        go log.Printf("Error:GetTopPlayers: Game %s", gameName)
        return "json error"
    }
    topResultsVal := reflect.ValueOf(topResults)
    resultCount := topResultsVal.Len()
    for i:=0; i<resultCount; i++ {
        _score,_  := topResultsVal.Index(i).Field(0).Interface().(float64)
        _pid,_  := topResultsVal.Index(i).Field(1).Interface().(string)
        _name,_ := gm.GetPlayerName(gameName, _pid)
        playeScores = append(playeScores, dt.PlayerScore{_name, _score})
    }

    sort.Sort(dt.ByScoreRev(playeScores))
 
    if topCount < int64(len(playeScores)) {
        playeScores = playeScores[:topCount]
    }
    go log.Printf("Info: GetTopPlayers: top %d playeScores: %v", topCount, playeScores)
    b, jerr := json.Marshal(playeScores)
    if jerr == nil {
        return string(b)
    } else {
        return "json error"
    }
}

// get score
func (gm * GameManager) GetPlayerHighScore(gameName string, playerId string) (float64, error) {
    return gm.rc.GetScore(gameName, playerId)
}

// store player new score for a week
func (gm * GameManager) StorePlayerScoreOnADay(gameName string, score float64, playerId string, numOfDaysOld int) {
    gm.rc.AddToSet(gameName+util.GetDate(numOfDaysOld), score, playerId)
}

func (gm * GameManager) DeletePlayerScoreOnADay(gameName string,  playerId string, numOfDaysOld int) (bool){
    gameName = gameName+util.GetDate(numOfDaysOld)
    redisRet, redisErr := gm.rc.RemScore(gameName, playerId)
    if redisErr != nil && redisErr.Error() != REDIS_NIL {
        go log.Printf("Error:DeletePlayerScoreOnADay: SUCESS gameName:%s, playerId:%s, redisErr:%v", gameName, playerId, redisErr)
        return false
    } else {
        go log.Printf("Info :DeletePlayerScoreOnADay: SUCESS gameName:%s,  playerId:%s, redisRet:%d", gameName, playerId, redisRet)
    }
    return true
}

// store player new score for a week
func (gm * GameManager) StorePlayerScoreDaily(gameName string, score float64, playerId string) {
    gm.rc.AddToSet(gameName+util.CurrentDate(), score, playerId)
}

// store player new score for a week
func (gm * GameManager) GetPlayerScoreOnDay(gameName string, playerId string, numOfDaysOld int) (float64, error) {
    return gm.GetPlayerHighScore(gameName+util.GetDate(numOfDaysOld), playerId)
}

// get top weekly 1000
func (gm * GameManager) GetTopPlayersOnDay(gameName string, topCount int64, numOfDaysOld int) (string) {
    if numOfDaysOld > 6 {
      return "";
    }

    playeScores := make([]dt.PlayerScore, 0)

    dateGameName := gameName+util.GetDate(numOfDaysOld)

    topResults, err := gm.rc.GetTop(dateGameName, topCount)
    if err != nil && err.Error() != REDIS_NIL{
        go log.Printf("Error:GetTopPlayersOnDay: dateGameName %s", dateGameName)
        return "json error"
    }
    topResultsVal := reflect.ValueOf(topResults)
    resultCount := topResultsVal.Len()
    for i:=0; i<resultCount; i++ {
        _score,_  := topResultsVal.Index(i).Field(0).Interface().(float64)
        _pid,_  := topResultsVal.Index(i).Field(1).Interface().(string)
        _name,_ := gm.GetPlayerName(gameName, _pid)
        playeScores = append(playeScores, dt.PlayerScore{_name, _score})
    }

    sort.Sort(dt.ByScoreRev(playeScores))
 
    if topCount < int64(len(playeScores)) {
        playeScores = playeScores[:topCount]
    }
    go log.Printf("Info: GetTopPlayersOnDay: top %d playeScores: %v", topCount, playeScores)
    b, jerr := json.Marshal(playeScores)
    if jerr == nil {
        return string(b)
    } else {
        return "json error"
    }

}

func (gm * GameManager) GetTopPlayersThisWeek(gameName string, topCount int64) (string) {
    playeScores := make([]dt.PlayerScore, 0)
    donePersons := make(map[string] bool) 

    for i:=6; i>=0; i--{
        topResults, err := gm.rc.GetTop(gameName+util.GetDate(i), topCount)
        if err != nil && err.Error() != REDIS_NIL{
            go log.Printf("Error:GetTopPlayersThisWeek: Game %s", gameName)
            continue
        }
        topResultsVal := reflect.ValueOf(topResults)
        resultCount := topResultsVal.Len()
        for i:=0; i<resultCount; i++ {
            _score,_  := topResultsVal.Index(i).Field(0).Interface().(float64)
            _pid,_  := topResultsVal.Index(i).Field(1).Interface().(string)
            _name,_ := gm.GetPlayerName(gameName, _pid)
            playeScores = append(playeScores, dt.PlayerScore{_name, _score})
        }
    }

    sort.Sort(dt.ByScoreRev(playeScores))

    for i:=0; i<len(playeScores) && i<int(topCount); i++ {
        if _,ok := donePersons[playeScores[i].N]; !ok {
            donePersons[playeScores[i].N] = true
        } else {
            playeScores = append(playeScores[:i], playeScores[i+1:]...) // perfectly fine if i is the last element https://play.golang.org/p/hcUEguyiTC
            i--
        }
    }
 
    if topCount < int64(len(playeScores)) {
        playeScores = playeScores[:topCount]
    }
    go log.Printf("Info: GetTopPlayersThisWeek: top %d playeScores: %v", topCount, playeScores)
    b, jerr := json.Marshal(playeScores)
    if jerr == nil {
        return string(b)
    } else {
        return "json error"
    }
}

// get rank among friends
func (gm * GameManager) GetScoreOfFriends(gameName string, playerId string, friendIds []string) (string) {
    var topPlayersWithScores dt.ResponseData
    playerScore, err := gm.GetPlayerHighScore(gameName, playerId)
    if err != nil && err.Error() != REDIS_NIL {
        go log.Printf("Error:GetScoreOfFriends: Error: %v", err)
        return ""
    }
    
    totalCount := len(friendIds) + 1
    topPlayersWithScores.PlayerIds = make([]string, totalCount)
    topPlayersWithScores.Scores = make([]float64, totalCount)
    for i:=1; i<totalCount; i++ {
        topPlayersWithScores.PlayerIds[i] = friendIds[i-1]
        topPlayersWithScores.Scores[i] = -1
        score, err1 := gm.GetPlayerHighScore(gameName, friendIds[i-1])
        if err1 != nil && err1.Error() != REDIS_NIL {
            go log.Printf("Error:GetScoreOfFriends: Game %s, %v", gameName, err1)
        } else {
            topPlayersWithScores.Scores[i] = score
        }
    }
    
    topPlayersWithScores.PlayerIds[0] = playerId
    topPlayersWithScores.Scores[0] =playerScore
    b, jerr := json.Marshal(topPlayersWithScores)
    if jerr == nil {
        return string(b)
    } else {
        return "json error"
    }
}

Here is the test code:

package gamedatamanager_test

import (
    dt   "gogameserver/datatypes"
    gdm  "gogameserver/gamedatamanager"
    util "gogameserver/util"

    "testing"
    "math/rand"
    "time"
    "strconv"
    "encoding/json"

    "sort"
)

const GAMENAME string  = "00dummygame" 
const PLAYERID string  = "00playerid"
var PLAYERIDS  = [] string{"00NeverAddThispid0", "00NeverAddThispid1",  "00NeverAddThispid3", "00NeverAddThispid4", "00NeverAddThispid5"}

func TestStorePlayerData(t *testing.T) {
    gm := gdm.New()
    playerData := dt.NewWithId(PLAYERID) 
    done := gm.StorePlayerData(GAMENAME, playerData)
    if !done {
        t.Errorf("Data shall have been stored\n")
    } 
    gm.DelPlayerData(GAMENAME, PLAYERID)
}

func TestGetPlayerData(t *testing.T) {
    gm := gdm.New()
    playerData := dt.NewWithId(PLAYERID) 
    done := gm.StorePlayerData(GAMENAME, playerData)
    if !done {
        t.Errorf("Error: Data shall have been stored\n")
    } 
    playerDataStr := dt.Str(playerData)
    playerDataStrFromDB, found := gm.GetPlayerData(GAMENAME, PLAYERID)
    if !found {
        t.Errorf("Error: GetPlayerData: Data shall have been found\n")
    } else {
        if playerDataStrFromDB != playerDataStr {
            t.Errorf("Error: playerDataStr from redis: %s\n\tplayerDataStr shall have been %s\n", playerDataStrFromDB, playerDataStr)
        }
    }
    gm.DelPlayerData(GAMENAME, PLAYERID)
}

func TestStorePlayerScore(t *testing.T) {
    gm := gdm.New()
    playerData := dt.NewWithId(PLAYERID) 
    playerData.A = 10.0
    newScore := 10000.0
    gm.DeletePlayerScore(GAMENAME, PLAYERID)
    gm.StorePlayerData(GAMENAME, playerData)
    success := gm.StorePlayerScore(GAMENAME, newScore, PLAYERID)
    if !success {
        t.Errorf("Error: StorePlayerScore: Data shall have been found\n")
    } else {
        newHiScore, err := gm.GetPlayerHighScore(GAMENAME, PLAYERID)
        if err != nil {
            t.Errorf("TestStorePlayerScore Error: GetPlayerHighScore, Error: %v\n", err)
        } else {
            if newHiScore != newScore {
                t.Errorf("TestStorePlayerScore Error: newHiScore=%d. Added newScore=%d\n", newHiScore, newScore)
            }
            playerDataStrFromDB, success := gm.GetPlayerData(GAMENAME, PLAYERID)
            if !success {
                t.Errorf("TestStorePlayerScore Error: GetPlayerData\n")
            } else {
                playerData.A = newScore
                playerDataStr :=  dt.Str(playerData)
                if playerDataStrFromDB != playerDataStr {
                    t.Errorf("TestStorePlayerScore Error: playerDataStr from redis: %s\n\tplayerDataStr shall have been %s\n", playerDataStrFromDB, playerDataStr)
                }
            }
        }
    }
    gm.DelPlayerData(GAMENAME, PLAYERID)
    gm.DeletePlayerScore(GAMENAME, PLAYERID)
}

func TestGetPlayerRank(t *testing.T) {
    gm := gdm.New()
    scores := []float64{2,1,7,4, 3}
    for i:=0; i<5; i++ {
        gm.DeletePlayerScore(GAMENAME, PLAYERIDS[i])
        playerData := dt.NewWithId(PLAYERIDS[i]) 
        playerData.A = scores[i]
        gm.StorePlayerData(GAMENAME, playerData)
        gm.StorePlayerScore(GAMENAME, scores[i], PLAYERIDS[i])
    }
    rank := gm.GetPlayerRank(GAMENAME, PLAYERIDS[2])
    if rank != 1{
        t.Errorf("TestGetPlayerRank Error: Player rank should have been 1 but is : %d\n", rank)
    }
    for i:=0; i<5; i++ {
        gm.DelPlayerData(GAMENAME, PLAYERIDS[i])
        gm.DeletePlayerScore(GAMENAME, PLAYERIDS[i])
    }
}


func TestGetScoreOfFriends(t *testing.T) {
    gm := gdm.New()
    scores := []float64{2,1,7,4, 3}
    for i:=0; i<5; i++ {
        gm.DeletePlayerScore(GAMENAME, PLAYERIDS[i])
        playerData := dt.NewWithId(PLAYERIDS[i]) 
        playerData.A = scores[i]
        gm.StorePlayerData(GAMENAME, playerData)
        gm.StorePlayerScore(GAMENAME, scores[i], PLAYERIDS[i])
    }
    scoresOfFriends := gm.GetScoreOfFriends(GAMENAME, PLAYERIDS[0], PLAYERIDS[2:5])
    scoresOfFriendsExpectedStr := "{\"PlayerIds\":[\"00NeverAddThispid0\",\"00NeverAddThispid3\",\"00NeverAddThispid4\",\"00NeverAddThispid5\"],\"Scores\":[2,7,4,3]}"
    if scoresOfFriends != scoresOfFriendsExpectedStr{
        t.Errorf("TestGetScoreOfFriends Error: scoresOfFriends str is : %s\n", scoresOfFriends)
    }

    for i:=0; i<5; i++ {
        gm.DelPlayerData(GAMENAME, PLAYERIDS[i])
        gm.DeletePlayerScore(GAMENAME, PLAYERIDS[i])
    }
}

func TestGetTopPlayers(t *testing.T) {
    gm := gdm.New()
    scores := []float64{2,1,7,4, 3}
    names  := make([] string, 5)

    playeScores := make([]dt.PlayerScore, 0)
    gm.DelKey(GAMENAME)

    for i:=0; i<5; i++ {
        names[i] = util.RandStringRunes(5)
        gm.DeletePlayerScore(GAMENAME, PLAYERIDS[i])
        playerData := dt.NewWithId(PLAYERIDS[i]) 
        playerData.A = scores[i]
        playerData.N = names[i]
        gm.StorePlayerData(GAMENAME, playerData)
        gm.StorePlayerScore(GAMENAME, scores[i], PLAYERIDS[i])

        playeScores = append(playeScores, dt.PlayerScore{names[i], scores[i]})
    }

    sort.Sort(dt.ByScoreRev(playeScores))
    topCount := 3
    top3 := gm.GetTopPlayers(GAMENAME, int64(topCount) )
    
    var top3Scorers []dt.PlayerScore
    json.Unmarshal([]byte(top3), &top3Scorers)

    for i:=0; i<topCount; i++{
        if playeScores[i].N != top3Scorers[i].N || playeScores[i].S != top3Scorers[i].S {
            t.Errorf("Error: TestGetTopPlayers,  playeScores[i] : %v\n, top3Scorers[i]: %v\n", playeScores[i], top3Scorers[i])
        }
    }

    for i:=0; i<5; i++ {
        gm.DelPlayerData(GAMENAME, PLAYERIDS[i])
        gm.DeletePlayerScore(GAMENAME, PLAYERIDS[i])
    }

} 

func TestGetTopPlayersThisWeek(t *testing.T) {
    gm := gdm.New()

    r := rand.New(rand.NewSource(time.Now().UnixNano()))
   
    scores := make([] float64, 0)
    names  := make([] string, 10)
    id     := make([] string, 10)

    for i:=0; i<10; i++ {
        names[i] = util.RandStringRunes(5)
        id[i] = strconv.Itoa(i)
        playerData := dt.NewWithId(id[i]) 
        playerData.N = names[i]
        gm.StorePlayerData(GAMENAME, playerData)
    }


    playerMaxScore := make(map[string] float64)
    for i:=0; i<10; i++ {
        playerMaxScore[names[i]] = 0
    }

    // for 7 days
    for d:=0; d<7; d++ {
        gm.DelKey(GAMENAME+util.GetDate(d))
        for i:=0; i<10; i++ {
            score := r.Float64()*100
            scores = append(scores, score)
            if playerMaxScore[names[i]] < score {
               playerMaxScore[names[i]] = score
            }
            gm.StorePlayerScoreOnADay(GAMENAME, score, id[i], d) 
        }
    } 

    playeScores := make([]dt.PlayerScore, 0)
    for k,v := range playerMaxScore {
        playeScores = append(playeScores, dt.PlayerScore{k, v})
    }
   
    sort.Sort(dt.ByScoreRev(playeScores))

    topCount := 5
    topWeeklyScorersJson := gm.GetTopPlayersThisWeek(GAMENAME, int64(topCount) )
    
    var topWeeklyScorers []dt.PlayerScore
    json.Unmarshal([]byte(topWeeklyScorersJson), &topWeeklyScorers)

    for i:=0; i<topCount; i++{
        if playeScores[i].N != topWeeklyScorers[i].N || playeScores[i].S != topWeeklyScorers[i].S {
            t.Errorf("Error: TestGetTopPlayersThisWeek,  playeScores[i] : %v\n, topWeeklyScorers[i]: %v\n", playeScores[i], topWeeklyScorers[i])
        }
    }

    for i:=0; i<10; i++ {
        gm.DelPlayerData(GAMENAME, id[i])
    }

    for d:=0; d<7; d++ {
        for i:=0; i<10; i++ {
            gm.DeletePlayerScoreOnADay(GAMENAME ,  id[i], d)
        }
    }
}

Here is the complete GoGameServer: https://github.com/ediston/gogameserver

Please review it and tell me how can I write better code.