diff --git a/cmd/cli/main.go b/cmd/cli/main.go index d8e7c3c..b104a53 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -1,18 +1,79 @@ package main import ( + "fmt" "log" "os" + "strconv" + "strings" "git.tuxpa.in/a/card_id/common/game" + "git.tuxpa.in/a/card_id/common/game/card" ) func main() { - g, err := game.New().ReadFromRoot(os.Getenv("CARD_DATA_DIR")) + deal, err := card.NewDealer().ReadFromRoot(os.Getenv("CARD_DATA_DIR")) if err != nil { log.Panicln(err) } - log.Println(g.RandomLevel().RandomTable().RandomResult()) + g := game.Game{Dealer: deal, Lives: 3} + trial := g.CreateTrial(3, 1) + for { + if trial != nil { + if trial.CheckSelection() { + fmt.Print("\n match found!!!") + } + } + fmt.Printf("\ncardid > ") + var args [4]string + alen, _ := fmt.Scanln(&args[0], &args[1], &args[2], &args[3]) + if alen < 0 { + continue + } + if strings.HasPrefix(args[0], "c") { + args[1] = string(strip([]byte(args[0]))) + args[0] = "c" + alen = 2 + } + switch args[0] { + case "help": + fmt.Printf("show, select, update") + case "show": + fmt.Printf("current hand:\n %+v", trial.Show()) + case "select", "sel", "c": + if alen > 1 { + num, _ := strconv.Atoi(args[1]) + cd, err := trial.SelectCard(num) + if err != nil { + log.Println(err) + } + if cd.Id > 0 { + msg := fmt.Sprintf(` +card %d was %s | lives: %d +current hand: +%s`, num, cd.Name, trial.Lives, trial.ShowString()) + fmt.Printf(msg) + } + } else { + trial.SelectCard(0) + } + case "debug": + fmt.Println(trial) + case "exit", "quit", "q": + os.Exit(0) + } + } +} +func strip(s []byte) []byte { + n := 0 + for _, b := range s { + if ('0' <= b && b <= '9') || + b == ' ' { + s[n] = b + n++ + } + } + return s[:n] } diff --git a/common/data/result.go b/common/data/result.go index 296ed25..b08faaa 100644 --- a/common/data/result.go +++ b/common/data/result.go @@ -9,9 +9,9 @@ import ( type Results []Result type Result struct { - Itemid int `yaml:"Itemid"` - Name string `yaml:"Name"` - Rate float64 `yaml:"Rate"` + Id int `yaml:"Itemid"` + Name string `yaml:"Name"` + Rate float64 `yaml:"Rate"` } func NewResultsFromFile(path string) (Results, error) { diff --git a/common/errs/errors.go b/common/errs/errors.go new file mode 100644 index 0000000..cbe81b4 --- /dev/null +++ b/common/errs/errors.go @@ -0,0 +1,26 @@ +package errs + +import ( + "fmt" + "strings" +) + +func e(prefix string, f string, args ...any) error { + return fmt.Errorf("%s: "+f, prefix, args) +} + +func Logic(f string, a ...any) error { + return e("logic", f, a) +} + +func IsLogic(err error) bool { + return strings.HasPrefix("logic", err.Error()) +} + +func Invalid(f string, a ...any) error { + return e("invalid", f, a) +} + +func IsInvalid(err error) bool { + return strings.HasPrefix("invalid", err.Error()) +} diff --git a/common/game/card/brand.go b/common/game/card/brand.go new file mode 100644 index 0000000..a7909a9 --- /dev/null +++ b/common/game/card/brand.go @@ -0,0 +1,24 @@ +package card + +import ( + "lukechampine.com/frand" +) + +type Brand struct { + Name string + + SelectionWeight float64 + RewardDeck *Deck + SpecialDeck *Deck + SpecialChance float64 +} + +func (l *Brand) GetDeck() *Deck { + if l.SpecialChance == 0 { + return l.RewardDeck + } + if l.SpecialChance > (frand.Float64() * 100) { + return l.SpecialDeck + } + return l.RewardDeck +} diff --git a/common/game/card/card.go b/common/game/card/card.go new file mode 100644 index 0000000..4d2fcbb --- /dev/null +++ b/common/game/card/card.go @@ -0,0 +1,16 @@ +package card + +import "git.tuxpa.in/a/card_id/common/data" + +type Card struct { + Id int `json:"id"` + Name string `json:"name"` + Rate float64 `json:"rate"` +} + +func (c *Card) FromResult(d data.Result) Card { + c.Id = d.Id + c.Name = d.Name + c.Rate = d.Rate + return *c +} diff --git a/common/game/card/dealer.go b/common/game/card/dealer.go new file mode 100644 index 0000000..7295d33 --- /dev/null +++ b/common/game/card/dealer.go @@ -0,0 +1,70 @@ +package card + +import ( + "log" + "path" + "strconv" + + "git.tuxpa.in/a/card_id/common/data" + "lukechampine.com/frand" +) + +type Dealer struct { + Brands map[string]*Brand + TotalProbability float64 +} + +func NewDealer() *Dealer { + return &Dealer{ + Brands: make(map[string]*Brand, 12), + TotalProbability: 0, + } +} + +func (g *Dealer) ReadFromRoot(datadir string) (*Dealer, error) { + if datadir == "" { + datadir = "./data" + } + grades, err := data.NewGradesFromFile(path.Join(datadir, "GradeInfo.yaml")) + if err != nil { + return nil, err + } + for _, v := range grades { + lvl := &Brand{} + lvl.Name = strconv.Itoa(v.Grade) + g.Brands[lvl.Name] = lvl + lvl.SelectionWeight = v.Rate + g.TotalProbability = g.TotalProbability + v.Rate + + rres, err := data.NewResultsFromFile(path.Join(datadir, "ResultTable", lvl.Name+".yaml")) + if err != nil { + return nil, err + } + lvl.RewardDeck = NewDeck(rres) + if v.SpecialRate > 0 { + lvl.SpecialChance = v.SpecialRate + sres, err := data.NewResultsFromFile(path.Join(datadir, "SpecialTable", lvl.Name+".yaml")) + if err != nil { + return nil, err + } + lvl.SpecialDeck = NewDeck(sres) + } + } + return g, nil +} + +func (g *Dealer) GetBrand() *Brand { + selector := frand.Float64() * g.TotalProbability + sofar := 0.0 + if len(g.Brands) == 0 { + return nil + } + for _, v := range g.Brands { + sofar = sofar + v.SelectionWeight + if sofar >= selector-0.000000001 { + return v + } + } + log.Println("BAD BAD BAD BAD BAD BAD PLEASE REPORT OH NO OH NO OH FISJAFIJNSAF") + return g.GetBrand() +} diff --git a/common/game/table/table.go b/common/game/card/deck.go similarity index 60% rename from common/game/table/table.go rename to common/game/card/deck.go index 841b980..bf4dd35 100644 --- a/common/game/table/table.go +++ b/common/game/card/deck.go @@ -1,4 +1,4 @@ -package table +package card import ( "fmt" @@ -9,33 +9,33 @@ import ( // a table is a collection of results -type Table struct { - raw data.Results +type Deck struct { + Cards []Card TotalProbability float64 } -func New(r data.Results) *Table { - out := &Table{} - out.raw = r - - for _, v := range out.raw { +func NewDeck(r data.Results) *Deck { + out := &Deck{} + out.Cards = make([]Card, 0, len(r)) + for _, v := range r { + out.Cards = append(out.Cards, new(Card).FromResult(v)) out.TotalProbability = out.TotalProbability + v.Rate } - return out } -func (t *Table) RandomResult() data.Result { +func (t *Deck) Draw() Card { selector := frand.Float64() * t.TotalProbability sofar := 0.0 - for _, v := range t.raw { + for _, v := range t.Cards { sofar = sofar + v.Rate if sofar >= selector-0.000000001 { return v } } - return data.Result{ + return Card{ + Id: 0, Name: fmt.Sprintf("please report this bug: %v", selector), Rate: selector, } diff --git a/common/game/game.go b/common/game/game.go index 1fb0530..7cfb910 100644 --- a/common/game/game.go +++ b/common/game/game.go @@ -1,72 +1,45 @@ package game import ( - "log" - "path" - "strconv" - - "git.tuxpa.in/a/card_id/common/data" - "git.tuxpa.in/a/card_id/common/game/level" - "git.tuxpa.in/a/card_id/common/game/table" + "git.tuxpa.in/a/card_id/common/game/card" "lukechampine.com/frand" ) type Game struct { - Levels map[string]*level.Level - TotalProbability float64 + Lives int + Dealer *card.Dealer } -func New() *Game { - return &Game{ - Levels: make(map[string]*level.Level, 12), - TotalProbability: 0, +func (g *Game) CreateTrial(pairs int, extra int) *Trial { + if pairs > 10 { + pairs = 10 } -} + cards := make([]card.Card, 0, pairs*2+extra) + ids := map[int]struct{}{} + deck := g.Dealer.GetBrand().GetDeck() -func (g *Game) ReadFromRoot(datadir string) (*Game, error) { - if datadir == "" { - datadir = "./data" - } - grades, err := data.NewGradesFromFile(path.Join(datadir, "GradeInfo.yaml")) - if err != nil { - return nil, err - } - for _, v := range grades { - lvl := &level.Level{} - lvl.Name = strconv.Itoa(v.Grade) - g.Levels[lvl.Name] = lvl - lvl.SelectionWeight = v.Rate - g.TotalProbability = g.TotalProbability + v.Rate - - rres, err := data.NewResultsFromFile(path.Join(datadir, "ResultTable", lvl.Name+".yaml")) - if err != nil { - return nil, err - } - lvl.RewardTable = table.New(rres) - if v.SpecialRate > 0 { - lvl.SpecialChance = v.SpecialRate - sres, err := data.NewResultsFromFile(path.Join(datadir, "SpecialTable", lvl.Name+".yaml")) - if err != nil { - return nil, err - } - lvl.SpecialTable = table.New(sres) + for i := 0; i < pairs; { + card := deck.Draw() + if _, ok := ids[card.Id]; !ok { + ids[card.Id] = struct{}{} + cards = append(cards, card, card) + i = i + 1 } } - return g, nil -} -func (g *Game) RandomLevel() *level.Level { - selector := frand.Float64() * g.TotalProbability - sofar := 0.0 - if len(g.Levels) == 0 { - return nil - } - for _, v := range g.Levels { - sofar = sofar + v.SelectionWeight - if sofar >= selector-0.000000001 { - return v + for i := 0; i < extra; { + card := deck.Draw() + if _, ok := ids[card.Id]; !ok { + ids[card.Id] = struct{}{} + cards = append(cards, card) + i = i + 1 } } - log.Println("BAD BAD BAD BAD BAD BAD PLEASE REPORT OH NO OH NO OH FISJAFIJNSAF") - return g.RandomLevel() + + frand.Shuffle(len(cards), func(i, j int) { cards[i], cards[j] = cards[j], cards[i] }) + trial := NewTrial(g.Lives) + for k, v := range cards { + trial.Cards[k+1] = v + } + return trial } diff --git a/common/game/level/level.go b/common/game/level/level.go deleted file mode 100644 index 5691131..0000000 --- a/common/game/level/level.go +++ /dev/null @@ -1,25 +0,0 @@ -package level - -import ( - "git.tuxpa.in/a/card_id/common/game/table" - "lukechampine.com/frand" -) - -type Level struct { - Name string - - SelectionWeight float64 - RewardTable *table.Table - SpecialTable *table.Table - SpecialChance float64 -} - -func (l *Level) RandomTable() *table.Table { - if l.SpecialChance == 0 { - return l.RewardTable - } - if l.SpecialChance > (frand.Float64() * 100) { - return l.SpecialTable - } - return l.RewardTable -} diff --git a/common/game/trial.go b/common/game/trial.go new file mode 100644 index 0000000..a3613a2 --- /dev/null +++ b/common/game/trial.go @@ -0,0 +1,124 @@ +package game + +import ( + "fmt" + "sort" + "strings" + + "git.tuxpa.in/a/card_id/common/errs" + "git.tuxpa.in/a/card_id/common/game/card" + "git.tuxpa.in/a/card_id/common/util" +) + +type Trial struct { + Cards map[int]card.Card + Lives int + + SelectionState int + PendingSelection [2]int + + Shown map[int]card.Card + History [][2]int +} + +func NewTrial(lives int) *Trial { + return &Trial{ + Lives: lives, + Cards: make(map[int]card.Card, 7), + Shown: make(map[int]card.Card, 7), + PendingSelection: [2]int{-1, -1}, + } +} + +func (t *Trial) SelectCard(id int) (card.Card, error) { + if id > len(t.Cards) { + return card.Card{}, errs.Invalid("index out of bounds") + } + if t.Lives == 0 { + return card.Card{}, errs.Logic("game over") + } + switch t.SelectionState { + case 0: // start selection + t.SelectionState = 1 + t.PendingSelection[0] = id + return t.Cards[id], nil + case 1: // complete selection + t.SelectionState = 2 + t.PendingSelection[1] = id + return t.Cards[id], nil + default: + return card.Card{}, errs.Logic("bad game state") + } +} + +func (t *Trial) CheckSelection() bool { + switch t.SelectionState { + case 0, 1: + return false + default: + } + + defer func() { + t.SelectionState = 0 + }() + + c1, ok := t.Cards[t.PendingSelection[0]] + if !ok { + return false + } + c2, ok := t.Cards[t.PendingSelection[1]] + if !ok { + return false + } + if c1.Id == c2.Id { + if c1.Name == c2.Name { + t.Shown[t.PendingSelection[0]] = c1 + t.Shown[t.PendingSelection[1]] = c2 + return true + } + } + t.History = append(t.History, t.PendingSelection) + t.PendingSelection = [2]int{-1, -1} + t.Lives = t.Lives - 1 + return false +} + +func (t *Trial) Show() map[int]card.Card { + out := util.CopyMap(t.Shown) + for _, v := range t.PendingSelection { + if v > 0 { + out[v] = t.Cards[v] + } + } + return out +} + +func (t *Trial) ShowString() string { + out := util.CopyMap(t.Shown) + for _, v := range t.PendingSelection { + if v > 0 { + out[v] = t.Cards[v] + } + } + + outs := "" + for _, k := range t.CardKeys() { + piece := "[???]" + if v, ok := out[k]; ok { + piece = fmt.Sprintf("[%s]", v.Name) + } + outs = outs + piece + " " + } + return strings.TrimSpace(outs) +} + +func (t *Trial) CardKeys() []int { + keys := make([]int, 0, len(t.Cards)) + for k := range t.Cards { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + return keys +} diff --git a/common/util/mapcopy.go b/common/util/mapcopy.go new file mode 100644 index 0000000..a238fcb --- /dev/null +++ b/common/util/mapcopy.go @@ -0,0 +1,9 @@ +package util + +func CopyMap[T comparable, V any](m map[T]V) map[T]V { + cp := make(map[T]V, len(m)) + for k, v := range m { + cp[k] = v + } + return cp +} diff --git a/data/ResultTable/12.yaml b/data/ResultTable/12.yaml index 0ae8381..0a44725 100644 --- a/data/ResultTable/12.yaml +++ b/data/ResultTable/12.yaml @@ -1,127 +1,127 @@ --- -- ItemID: 2027 +- Itemid: 2027 Name: Light pink potion Rate: 5.000000 -- ItemID: 2227 +- Itemid: 2227 Name: Light blue potion Rate: 5.000000 -- ItemID: 8331 +- Itemid: 8331 Name: Universal Drill 8 Rate: 5.000000 -- ItemID: 8332 +- Itemid: 8332 Name: Sand Drill 8 Rate: 5.000000 -- ItemID: 8333 +- Itemid: 8333 Name: Sea Drill 8 Rate: 5.000000 -- ItemID: 8334 +- Itemid: 8334 Name: Land Drill 8 Rate: 5.000000 -- ItemID: 8335 +- Itemid: 8335 Name: Stone 8 Rate: 5.000000 -- ItemID: 8336 +- Itemid: 8336 Name: I do not know how to do this Rate: 5.000000 -- ItemID: 8369 +- Itemid: 8369 Name: Neo Sand Drill 8 Rate: 2.000000 -- ItemID: 8371 +- Itemid: 8371 Name: Neo Land Drill 8 Rate: 2.000000 -- ItemID: 8372 +- Itemid: 8372 Name: Neo Dull 8 Rate: 2.000000 -- ItemID: 8370 +- Itemid: 8370 Name: Neo Sea Drill 8 Rate: 2.000000 -- ItemID: 61850 +- Itemid: 61850 Name: Card Pack of Stars No.7 Rate: 5.000000 -- ItemID: 1008 +- Itemid: 1008 Name: Diamond Rate: 2.000000 -- ItemID: 60403 +- Itemid: 60403 Name: Stone of detection 380 Rate: 10.000000 -- ItemID: 60416 +- Itemid: 60416 Name: stone of attack power 380 Rate: 10.000000 -- ItemID: 60429 +- Itemid: 60429 Name: Mana stone 380 Rate: 10.000000 -- ItemID: 60442 +- Itemid: 60442 Name: Stone of Magic Defense 380 Rate: 10.000000 -- ItemID: 60455 +- Itemid: 60455 Name: Stone of the Master 380 Rate: 10.000000 -- ItemID: 60468 +- Itemid: 60468 Name: Stone of hit rate 380 Rate: 10.000000 -- ItemID: 60481 +- Itemid: 60481 Name: Stone of weight 380 Rate: 10.000000 -- ItemID: 60494 +- Itemid: 60494 Name: Stone of Agile 380 Rate: 10.000000 -- ItemID: 60507 +- Itemid: 60507 Name: stone of defense 380 Rate: 10.000000 -- ItemID: 60520 +- Itemid: 60520 Name: Stone of Luck 380 Rate: 10.000000 -- ItemID: 60533 +- Itemid: 60533 Name: Stone of Stamina 380 Rate: 10.000000 -- ItemID: 60546 +- Itemid: 60546 Name: Avoided stone 380 Rate: 10.000000 -- ItemID: 60404 +- Itemid: 60404 Name: Stone of detection 395 Rate: 10.000000 -- ItemID: 60417 +- Itemid: 60417 Name: Stone of ATK 395 Rate: 10.000000 -- ItemID: 60430 +- Itemid: 60430 Name: Mana 395 Rate: 10.000000 -- ItemID: 60443 +- Itemid: 60443 Name: Stone of Magic Defense 395 Rate: 10.000000 -- ItemID: 60456 +- Itemid: 60456 Name: Stone of the Master 395 Rate: 10.000000 -- ItemID: 60469 +- Itemid: 60469 Name: Stone of hit rate 395 Rate: 10.000000 -- ItemID: 60482 +- Itemid: 60482 Name: Stone of weight 395 Rate: 10.000000 -- ItemID: 60495 +- Itemid: 60495 Name: Stone of Agile 395 Rate: 10.000000 -- ItemID: 60508 +- Itemid: 60508 Name: stone of defense 395 Rate: 10.000000 -- ItemID: 60521 +- Itemid: 60521 Name: Stone of Luck 395 Rate: 10.000000 -- ItemID: 60534 +- Itemid: 60534 Name: Stone of Stamina 395 Rate: 10.000000 -- ItemID: 60547 +- Itemid: 60547 Name: Avoidance Stone 395 Rate: 10.000000 -- ItemID: 2003 +- Itemid: 2003 Name: Hanbang Punction Rate: 30.000000 -- ItemID: 2203 +- Itemid: 2203 Name: Gold Pearment Potion Rate: 30.000000 -- ItemID: 2004 +- Itemid: 2004 Name: 천 엑 스 Rate: 15.000000 -- ItemID: 2204 +- Itemid: 2204 Name: Jewelry Potion Rate: 15.000000 diff --git a/data/ResultTable/5.yaml b/data/ResultTable/5.yaml index f3cb0a6..60c80ca 100644 --- a/data/ResultTable/5.yaml +++ b/data/ResultTable/5.yaml @@ -5,110 +5,110 @@ - Itemid: 2203 Name: Gold Pearment Potion Rate: 30.000000 -- ItemID: 8308 +- Itemid: 8308 Name: Universal Drill No. 3 Rate: 5.000000 -- ItemID: 8309 +- Itemid: 8309 Name: sand drill 3 Rate: 5.000000 -- ItemID: 8310 +- Itemid: 8310 Name: ground drill 3 Rate: 5.000000 -- ItemID: 8311 +- Itemid: 8311 Name: Stone 3 Rate: 5.000000 -- ItemID: 8346 +- Itemid: 8346 Name: Neo Sand Drill No. 3 Rate: 2.000000 -- ItemID: 8347 +- Itemid: 8347 Name: Neo Land Drill No. 3 Rate: 2.000000 -- ItemID: 8348 +- Itemid: 8348 Name: Neo Dry L Deal 3 Rate: 2.000000 -- ItemID: 61758 +- Itemid: 61758 Name: Card Pack of Stars No.3 Rate: 5.000000 -- ItemID: 1004 +- Itemid: 1004 Name: Topaz Rate: 10.000000 -- ItemID: 1005 +- Itemid: 1005 Name: Emerald Rate: 8.000000 -- ItemID: 60011 +- Itemid: 60011 Name: Stone of detection 170 Rate: 10.000000 -- ItemID: 60025 +- Itemid: 60025 Name: Stone of ATK 170 Rate: 10.000000 -- ItemID: 60039 +- Itemid: 60039 Name: Mana's stone 170 Rate: 10.000000 -- ItemID: 60053 +- Itemid: 60053 Name: Magic defense stone 170 Rate: 10.000000 -- ItemID: 60067 +- Itemid: 60067 Name: Stone of the magic power 170 Rate: 10.000000 -- ItemID: 60081 +- Itemid: 60081 Name: Stone of hit rate 170 Rate: 10.000000 -- ItemID: 60095 +- Itemid: 60095 Name: Stone of weight 170 Rate: 10.000000 -- ItemID: 60109 +- Itemid: 60109 Name: Stone of Agile Stone 170 Rate: 10.000000 -- ItemID: 60123 +- Itemid: 60123 Name: Stone of defense 170 Rate: 10.000000 -- ItemID: 60137 +- Itemid: 60137 Name: Stone of Luck 170 Rate: 10.000000 -- ItemID: 60151 +- Itemid: 60151 Name: Stone of Stamina 170 Rate: 10.000000 -- ItemID: 60165 +- Itemid: 60165 Name: Avoided stone 170 Rate: 10.000000 -- ItemID: 60012 +- Itemid: 60012 Name: Stone of detection 185 Rate: 10.000000 -- ItemID: 60026 +- Itemid: 60026 Name: Stone of ATK 185 Rate: 10.000000 -- ItemID: 60040 +- Itemid: 60040 Name: Mana's stone 185 Rate: 10.000000 -- ItemID: 60054 +- Itemid: 60054 Name: Stone of Magic Defense 185 Rate: 10.000000 -- ItemID: 60068 +- Itemid: 60068 Name: Stone of the Master 185 Rate: 10.000000 -- ItemID: 60082 +- Itemid: 60082 Name: Stone of hit rate 185 Rate: 10.000000 -- ItemID: 60096 +- Itemid: 60096 Name: Stone of Weight 185 Rate: 10.000000 -- ItemID: 60110 +- Itemid: 60110 Name: Stone of Agile 185 Rate: 10.000000 -- ItemID: 60124 +- Itemid: 60124 Name: 185 defense stone 185 Rate: 10.000000 -- ItemID: 60138 +- Itemid: 60138 Name: Stone of Luck 185 Rate: 10.000000 -- ItemID: 60152 +- Itemid: 60152 Name: Stone of Stamina 185 Rate: 10.000000 -- ItemID: 60166 +- Itemid: 60166 Name: Avoidance of stone 185 Rate: 10.000000 - Itemid: 2004 - Name: 천 엑 스 + Name: Thousand extract Rate: 15.000000 - Itemid: 2204 Name: Jewelry Potion