gohbem

package module
v0.11.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 24, 2023 License: Apache-2.0 Imports: 10 Imported by: 0

README

Gohbem

Gohbem is an optimized judgemental library that computes PvP rankings for Pokemon GO.

This is a rewrite of node version https://github.com/Mygod/ohbem

Features

  • Little cup/great league/ultra league rankings
  • Multiple level caps (level 50/51)
  • Customizable CP/level caps
  • Evolutions support
  • Mega evolutions support (including unreleased Mega)
  • Tyrogue evolutions support
  • Gender-locked evolutions support
  • Unevolvable costumes support
  • Tied PvP ranks (for example, 13/15/14 and 13/15/15 Talonflame are both UL rank 1 at L51, followed by 14/14/14 being UL rank 3)
  • Functionally perfect support
  • Optional built-in caching
  • Faster than node :)

Current State

  • CalculateTopRanks is broken.
  • Everything else is fine.

Documentation

Usage

package main

import (
    "github.com/UnownHash/gohbem"
)

func main() {
    var leagues = map[string]gohbem.League{                          // Leagues configuration & caps.
        "little": {                                                   // Cap for master is ignored.
            Cap:            500,
            LittleCupRules: true,
        },
        "great": {
            Cap:            1500,
            LittleCupRules: false,
        },
        "ultra": {
            Cap:            2500,
            LittleCupRules: false,
        },
        "master": {
            Cap:            0,
            LittleCupRules: false,
        },
    }
    levelCaps := []int{50, 51}                                        // Level caps.

    ohbem := gohbem.Ohbem{Leagues: leagues, LevelCaps: levelCaps}

    err = ohbem.FetchPokemonData()                                    // Fetch latest stable MasterFile...
    err = ohbem.WatchPokemonData()                                    // ...automatically watch remote for changes...
    err = ohbem.LoadPokemonData("masterfile.json")                    // ...or load from file

    // ...
}

Examples

Provided examples are marshaled. Each method is returning defined structs. Read Documentation for details.

QueryPvPRank
entries, err := ohbem.QueryPvPRank(605, 0, 0, 1, 1, 4, 12, 7)
{
  "great":[
    {"pokemon":605,"cap":50,"value":1444316,"level":50,"cp":1348,"percentage":0.84457,"rank":3158},
    {"pokemon":605,"cap":51,"value":1472627,"level":51,"cp":1364,"percentage":0.85568,"rank":3128},
    {"pokemon":606,"cap":40,"value":1639371,"level":21,"cp":1493,"percentage":0.97919,"rank":197,"capped":true}
  ],
  "little":[
    {"pokemon":605,"cap":40,"value":320801,"level":14.5,"cp":494,"percentage":0.95123,"rank":548,"capped":true},
    {"pokemon":606,"cap":40,"value":302917,"level":7,"cp":486,"percentage":0.93383,"rank":1056,"capped":true}
  ],
  "ultra":[
    {"pokemon":606,"cap":40,"value":3519629,"level":40,"cp":2489,"percentage":0.97294,"rank":651},
    {"pokemon":606,"cap":50,"value":3519629,"level":40,"cp":2489,"percentage":0.97294,"rank":745,"capped":true}
  ]
}
CalculateTopRanks (broken)
entries, err := ohbem.CalculateTopRanks(5, 605, 0, 0, 0)
{
  "great":[
    {"value":1710113,"level":50,"cp":1498,"percentage":1,"rank":1,"attack":8,"defense":15,"stamina":15,"cap":50},
    {"value":1699358,"level":48.5,"cp":1500,"percentage":0.99371,"rank":2,"attack":11,"defense":15,"stamina":15,"cap":50},
    {"value":1699151,"level":50,"cp":1489,"percentage":0.99359,"rank":3,"attack":7,"defense":15,"stamina":15,"cap":50},
    {"value":1698809,"level":49,"cp":1500,"percentage":0.99339,"rank":4,"attack":10,"defense":15,"stamina":15,"cap":50},
    {"value":1698192,"level":49.5,"cp":1494,"percentage":0.99303,"rank":5,"attack":9,"defense":15,"stamina":14,"cap":50},
    {"value":1698192,"level":49.5,"cp":1499,"percentage":0.99303,"rank":5,"attack":9,"defense":15,"stamina":15,"cap":50},
    {"value":1720993,"level":51,"cp":1497,"percentage":1,"rank":1,"attack":6,"defense":15,"stamina":15,"cap":51},
    {"value":1717106,"level":51,"cp":1500,"percentage":0.99774,"rank":2,"attack":7,"defense":14,"stamina":15,"cap":51},
    {"value":1710113,"level":50,"cp":1498,"percentage":0.99368,"rank":3,"attack":8,"defense":15,"stamina":15,"cap":51},
    {"value":1709818,"level":51,"cp":1487,"percentage":0.99351,"rank":4,"attack":5,"defense":15,"stamina":15,"cap":51},
    {"value":1709291,"level":50.5,"cp":1498,"percentage":0.9932,"rank":5,"attack":7,"defense":15,"stamina":15,"cap":51}
  ],
  "little":[
    {"value":337248,"level":14,"cp":500,"percentage":1,"rank":1,"attack":0,"defense":14,"stamina":15,"cap":40},
    {"value":335954,"level":14,"cp":500,"percentage":0.99616,"rank":2,"attack":0,"defense":15,"stamina":13,"cap":40},
    {"value":334290,"level":14,"cp":498,"percentage":0.99123,"rank":3,"attack":0,"defense":13,"stamina":15,"cap":40},
    {"value":333943,"level":14,"cp":500,"percentage":0.9902,"rank":4,"attack":1,"defense":15,"stamina":11,"cap":40},
    {"value":333571,"level":14,"cp":499,"percentage":0.98909,"rank":5,"attack":1,"defense":12,"stamina":15,"cap":40}
  ]
}
FilterLevelCaps
entries, err := ohbem.QueryPvPRank(661, 0, 0, 1, 15, 15, 14, 1)
filter := ohbem.FilterLevelCaps(entries["great"], []int{51})
[
  {"pokemon":662,"cap":51,"value":1743985,"level":41.5,"cp":1493,"percentage":0.94736,"rank":1328},
  {"pokemon":663,"cap":40,"value":1756548,"level":23.5,"cp":1476,"percentage":0.94144,"rank":2867,"capped":true}
]

Benchmark

TL;DR

  • Go QueryPvPRank is 5 times faster than node with disabled cache.
  • Go QueryPvPRank is 10 times faster than node with enabled cache.
Specs & versions
# Gohbem 0.7.3
# Ohbem 1.4.1
# cpu: 12th Gen Intel(R) Core(TM) i9-12900KF

$ go version
go version go1.19.4 linux/amd64
$ node --version
v16.14.0
QueryPvPRank
Gohbem
$ time ./main  # cache disabled ; maxPokemonId = 2
QueryPvPRank iterated 13068 in 1m23.355235694s

real    1m23.406s
user    1m29.580s
sys     0m4.767s

$ time ./main  # cache enabled ; maxPokemonId = 200
QueryPvPRank iterated 1306800 in 3.821691967s

real    0m3.898s
user    0m4.094s
sys     0m0.315s
Ohbem (node)
$ time node main.js  # cache disabled ; maxPokemonId = 2
queryPvPRank iterated 13068 in 418771ms

real    6m58.854s
user    7m7.976s
sys     0m17.332s

$ time node main.js  # cache enabled ; maxPokemonId = 200
queryPvPRank iterated 1306800 in 38922ms

real    0m39.038s
user    0m46.019s
sys     0m3.972s
Test scripts
main.js
const Ohbem = require('ohbem');
const pokemonData = require('./master-test.json');

async function test() {
    const ohbem = new Ohbem({
        leagues: {
        little: {
            little: false,
            cap: 500,
        },
        great: {
            little: false,
            cap: 1500,
        },
        ultra: {
            little: false,
            cap: 2500,
        },
        master: null,
    },
        levelCaps: [40, 50, 51],
        pokemonData,
        cachingStrategy: Ohbem.cachingStrategies.balanced // change
    });

    const maxPokemonId = 200; // change
    const maxAttack = 10;
    const maxDefense = 5;
    const maxStamina = 10;
    const maxLevel = 5;
    let counter = 0;

    const start = Date.now();
    for (let p = 1; p <= maxPokemonId; p++) {
        for (let a = 0; a <= maxAttack; a++) {
            for (let d = 0; d <= maxDefense; d++) {
                for (let s = 0; s <= maxStamina; s++) {
                    for (let l = 1; l <= maxLevel; l += 0.5) {
                        ohbem.queryPvPRank(p, 0, 0, 0, a, d, s, l);
                        counter++;
                    }
                }
            }
        }
    }
    const elapsed = Date.now() - start;
    console.log(`queryPvPRank iterated ${counter} in ${elapsed}ms`);
}

test();
main.go
package main

import (
	"fmt"
	"github.com/UnownHash/gohbem"
	"time"
)

func mainOne() {
	var leagues = map[string]gohbem.League{
		"little": {
			Cap:            500,
			LittleCupRules: false,
		},
		"great": {
			Cap:            1500,
			LittleCupRules: false,
		},
		"ultra": {
			Cap:            2500,
			LittleCupRules: false,
		},
		"master": {
			Cap:            0,
			LittleCupRules: false,
		},
	}

	levelCaps := []int{40, 50, 51}

	ohbem := gohbem.Ohbem{Leagues: leagues, LevelCaps: levelCaps, DisableCache: false}  // change
	_ = ohbem.LoadPokemonData("master-test.json")

	const (
		maxPokemonId = 200 // change
		maxAttack    = 10
		maxDefense   = 5
		maxStamina   = 10
		maxLevel     = 5
	)
	var counter uint

	start := time.Now()
	for p := 1; p <= maxPokemonId; p++ {
		for a := 0; a <= maxAttack; a++ {
			for d := 0; d <= maxDefense; d++ {
				for s := 0; s <= maxStamina; s++ {
					for l := 1.0; l <= maxLevel; l = l + 0.5 {
						ohbem.QueryPvPRank(p, 0, 0, 0, a, d, s, l)
						counter++
					}
				}
			}
		}
	}
	elapsed := time.Since(start)
	fmt.Printf("QueryPvPRank iterated %d in %s\n", counter, elapsed)

func main() {
	mainOne() // bench go
}

Documentation

Index

Constants

View Source
const MasterFileURL = "https://raw.githubusercontent.com/WatWowMap/Masterfile-Generator/master/master-latest-basics.json"

MasterFileURL is a remote address used to fetch MasterFile.

View Source
const MaxLevel = 100

MaxLevel handled by gohbem.

View Source
const VERSION = "0.10.0"

VERSION of gohbem, follows Semantic Versioning. (http://semver.org/)

Variables

View Source
var ErrLeaguesMissing = errors.New("leagues configuration is empty")

ErrLeaguesMissing is returned when Leagues configuration is empty.

View Source
var ErrLevelCapsMissing = errors.New("levelCaps configuration is empty")

ErrLevelCapsMissing is returned when levelCaps configuration is empty.

View Source
var ErrMasterFileDecode = errors.New("can't decode remote MasterFile")

ErrMasterFileDecode is returned when decode of MasterFile fail.

View Source
var ErrMasterFileFetch = errors.New("can't fetch remote MasterFile")

ErrMasterFileFetch is returned when remote fetch of MasterFile fail.

View Source
var ErrMasterFileMarshall = errors.New("can't marshal MasterFile")

ErrMasterFileMarshall is returned when Marshal of MasterFile fail.

View Source
var ErrMasterFileOpen = errors.New("can't open MasterFile")

ErrMasterFileOpen is returned when MasterFile can't be open.

View Source
var ErrMasterFileSave = errors.New("can't save MasterFile")

ErrMasterFileSave is returned when MasterFile can't be saved.

View Source
var ErrMasterFileUnloaded = errors.New("masterFile unloaded")

ErrMasterFileUnloaded is returned when MasterFile wasn't loaded but there was a need to use it.

View Source
var ErrMasterFileUnmarshall = errors.New("can't unmarshal MasterFile")

ErrMasterFileUnmarshall is returned when UnMarshal of MasterFile fail.

View Source
var ErrMissingPokemon = errors.New("missing pokemonID in MasterFile")

ErrMissingPokemon is returned when Pokemon is missing in MasterFile.

View Source
var ErrNilChannel = errors.New("can't close nil channel")

ErrNilChannel is returned when o.watcherChan is uninitialized.

View Source
var ErrPvpStatBestCp = errors.New("bestCP > cap")

ErrPvpStatBestCp is returned when BestCP > Cap in calculatePvPStat function.

View Source
var ErrQueryInputOutOfRange = errors.New("one of input arguments 'Attack, Defense, Stamina, Level' is out of range")

ErrQueryInputOutOfRange is returned when wrong arguments are passed to QueryPvPRank function.

View Source
var ErrWatcherStarted = errors.New("MasterFile Watcher Already Started")

ErrWatcherStarted is returned when MasterFile Watcher is already running.

Functions

func RankingComparatorDefault added in v0.10.0

func RankingComparatorDefault(a, b *PvPRankingStats) int

RankingComparatorDefault ranks everything by stat product descending then by attack descending. This is the default behavior, since in general, a higher stat product is usually preferable; and in case of tying stat products, higher attack means that you would be more likely to win CMP ties.

func RankingComparatorPreferHigherCp added in v0.10.0

func RankingComparatorPreferHigherCp(a, b *PvPRankingStats) int

RankingComparatorPreferHigherCp in addition to the default rules, also compare by CP descending in the end. While ties are not meaningfully different most of the time, the rationale here is that a higher CP looks more intimidating.

func RankingComparatorPreferLowerCp added in v0.10.0

func RankingComparatorPreferLowerCp(a, b *PvPRankingStats) int

RankingComparatorPreferLowerCp in addition to the default rules, also compare by CP ascending in the end. While ties are not meaningfully different most of the time, the rationale here is that you can flex beating your opponent using one with a lower CP.

Types

type Evolution

type Evolution struct {
	Pokemon           int `json:"pokemon"`
	Form              int `json:"form,omitempty"`
	GenderRequirement int `json:"gender_requirement,omitempty"`
}

Evolution entry represents row of Pokemon -> Evolution.

type Form

type Form struct {
	Attack                    int                  `json:"attack,omitempty"`
	Defense                   int                  `json:"defense,omitempty"`
	Stamina                   int                  `json:"stamina,omitempty"`
	Little                    bool                 `json:"little,omitempty"`
	Evolutions                []Evolution          `json:"evolutions,omitempty"`
	TempEvolutions            map[int]PokemonStats `json:"temp_evolutions,omitempty"`
	CostumeOverrideEvolutions []int                `json:"costume_override_evos,omitempty"`
}

Form entry represents row of Pokemon -> Form.

type League

type League struct {
	Cap            int  `json:"cap"`
	LittleCupRules bool `json:"little_cup_rules"`
}

League struct is holding one entry of League configuration passed to Ohbem struct.

type Logger added in v0.11.1

type Logger interface {
	Print(message string)
}

Logger interface

Example implementation:

type CustomLogger struct{}
func (cl *CustomLogger) Print(message string) {
	fmt.Println("CustomLogger:", message)
}
logger := &CustomLogger{}
ohbem := Ohbem{Logger: logger, ...}

Notes:

  • The implementation of the Logger interface defines the specific behavior of the logging operation.
  • The method should handle the formatting and output of the Log message according to the logger's rules.

type Ohbem

type Ohbem struct {
	PokemonData           PokemonData
	LevelCaps             []int
	Leagues               map[string]League
	DisableCache          bool
	RankingComparator     RankingComparator
	IncludeHundosUnderCap bool
	WatcherInterval       time.Duration

	Logger Logger
	// contains filtered or unexported fields
}

Ohbem struct is holding main configuration, cache and channels.

func (*Ohbem) CalculateCp added in v0.10.0

func (o *Ohbem) CalculateCp(pokemonId, form, evolution, attack, defense, stamina int, level float64) (int, error)

CalculateCp calculates CP for your pokemon. Errors if pokemon cannot be found in master.

func (*Ohbem) ClearCache

func (o *Ohbem) ClearCache()

func (*Ohbem) FetchPokemonData

func (o *Ohbem) FetchPokemonData() error

FetchPokemonData Fetch remote MasterFile and keep it in memory.

func (*Ohbem) FilterLevelCaps

func (o *Ohbem) FilterLevelCaps(entries []PokemonEntry, interestedLevelCaps []int) []PokemonEntry

FilterLevelCaps Filter the output of queryPvPRank with a subset of interested level caps.

func (*Ohbem) FindBaseStats

func (o *Ohbem) FindBaseStats(pokemonId int, form int, evolution int) (PokemonStats, error)

FindBaseStats Look up base stats of a Pokémon.

func (*Ohbem) IsMegaUnreleased

func (o *Ohbem) IsMegaUnreleased(pokemonId int, evolution int) (bool, error)

IsMegaUnreleased Check whether the stats for a given mega is speculated.

func (*Ohbem) LoadPokemonData

func (o *Ohbem) LoadPokemonData(filePath string) error

LoadPokemonData Load MasterFile from provided filePath and keep it in memory.

func (*Ohbem) QueryPvPRank

func (o *Ohbem) QueryPvPRank(pokemonId int, form int, costume int, gender int, attack int, defense int, stamina int, level float64) (map[string][]PokemonEntry, error)

QueryPvPRank Query all ranks for a specific Pokémon, including its possible evolutions.

func (*Ohbem) SavePokemonData

func (o *Ohbem) SavePokemonData(filePath string) error

SavePokemonData Save MasterFile from memory to provided location.

func (*Ohbem) StopWatchingPokemonData

func (o *Ohbem) StopWatchingPokemonData() error

StopWatchingPokemonData Stop watching for remote MasterFile changes.

func (*Ohbem) WatchPokemonData

func (o *Ohbem) WatchPokemonData() error

WatchPokemonData Watch for remote MasterFile changes. When new, auto-update and clean cache.

type Pokemon

type Pokemon struct {
	Attack                    int                  `json:"attack"`
	Defense                   int                  `json:"defense"`
	Stamina                   int                  `json:"stamina"`
	Little                    bool                 `json:"little,omitempty"`
	Evolutions                []Evolution          `json:"evolutions,omitempty"`
	TempEvolutions            map[int]PokemonStats `json:"temp_evolutions,omitempty"`
	CostumeOverrideEvolutions []int                `json:"costume_override_evos,omitempty"`
	Forms                     map[int]Form         `json:"forms"`
}

Pokemon entry represents row of Pokemon data from MasterFile

type PokemonData

type PokemonData struct {
	Initialized bool            `json:"-"`
	Pokemon     map[int]Pokemon `json:"pokemon"`
	Costumes    map[int]bool    `json:"costumes"`
}

PokemonData is a struct holding MasterFile data.

type PokemonEntry

type PokemonEntry struct {
	Pokemon    int     `json:"pokemon"`
	Form       int     `json:"form,omitempty"`
	Cap        float64 `json:"cap,omitempty"`
	Value      float64 `json:"value,omitempty"`
	Level      float64 `json:"level"`
	Cp         int     `json:"cp,omitempty"`
	Percentage float64 `json:"percentage"`
	Rank       int16   `json:"rank"`
	Capped     bool    `json:"capped,omitempty"`
	Evolution  int     `json:"evolution,omitempty"`
}

PokemonEntry is holding a row of result for QueryPvPRank and FilterLevelCaps functions.

type PokemonStats

type PokemonStats struct {
	Attack     int  `json:"attack,omitempty"`
	Defense    int  `json:"defense,omitempty"`
	Stamina    int  `json:"stamina,omitempty"`
	Unreleased bool `json:"unreleased,omitempty"`
}

PokemonStats entry represents basic Pokemon stats and mega release state.

type PvPRankingStats added in v0.10.0

type PvPRankingStats struct {
	Attack float64
	Value  float64
	Level  float64
	Cp     int
	Index  int
}

PvPRankingStats internal struct for comparison.

type Ranking

type Ranking struct {
	Value      float64 `json:"value"`
	Level      float64 `json:"level"`
	Cp         int     `json:"cp"`
	Percentage float64 `json:"percentage"`
	Rank       int16   `json:"rank"`
	Attack     int     `json:"attack"`
	Defense    int     `json:"defense"`
	Stamina    int     `json:"stamina"`
	Cap        float64 `json:"cap"`
	Capped     bool    `json:"capped,omitempty"`
	Index      int     `json:"index,omitempty"`
}

Ranking entry represents PvP row for Pokemon.

type RankingComparator added in v0.10.0

type RankingComparator func(a, b *PvPRankingStats) int

RankingComparator specifies how to sort rankings.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL