This commit is contained in:
a 2022-04-13 18:09:58 -05:00
parent 4a460e1dce
commit a5f78f4d9d
13 changed files with 452 additions and 174 deletions

View File

@ -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]
}

View File

@ -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) {

26
common/errs/errors.go Normal file
View File

@ -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())
}

24
common/game/card/brand.go Normal file
View File

@ -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
}

16
common/game/card/card.go Normal file
View File

@ -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
}

View File

@ -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()
}

View File

@ -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,
}

View File

@ -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
}

View File

@ -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
}

124
common/game/trial.go Normal file
View File

@ -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
}

9
common/util/mapcopy.go Normal file
View File

@ -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
}

View File

@ -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

View File

@ -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