# Riichi City Wall Generator 事源有用戶在Discord提出了麻雀一番街的[牌山生成](https://www.mahjong-jp.com/verify)有問題 https://discord.com/channels/941185061057888347/1009473385375473744/1070620515699527791 ```! Rinkashikachi 「114284079」 — 02/02/2023 08:23 GMT Speaking of random: @riichicity-frank Just noticed that you have your wall generator available for viewing. Ngl it is very bad. The seed is just based on current time. With such trivial algorithm you get awful randomizer enthropy and I am pretty sure that this "algorithm" wouldn't even cover all possible combinations of the wall in a sense that a lot of them are nearly 100% unlikely to happen. Are you guys planning to improve that? It is a pretty crucial part for "card" games like Mahjong. You might want to look into what Tenho does for example. Their seed generation looks properly complex: http://blog.tenhou.net/article/30503297.html http://blog.tenhou.net/article/174202532.html ``` For reference this is the disclosed wall generation code (may have typo cause I didn't check :P) SHA256 is used as hash check ```go= //https://www.mahjong-jp.com/verify package main import ( "fmt" "math/rand" "time" ) func sampleMahjongShuffle() { //init seed rand.Seed(time.Now().UnixNano()) //init cards mahjong := Cards{} mahjong.Reset() //shuffle card mahjong.Shuffle() //print fmt.Println(mahjong.Encode()) } var CARDS = []int16{ 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x105, // 0x105 0p 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, // 0x01~0x09 1~9p 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x115, // 0x115 0s 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, // 0x11~0x19 1~9s 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x125, // 0x125 0m 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x29, // 0x21~0x29 1~9m 0x31, 0x31, 0x31, 0x31, 0x41, 0x41, 0x41, 0x41, 0x51, 0x51, 0x51, 0x51, 0x61, 0x61, 0x61, 0x61, 0x71, 0x71, 0x71, 0x71, 0x81, 0x81, 0x81, 0x81, 0x91, 0x91, 0x91, 0x91, } type Cards []int16 func (cs *Cards) Shuffle() { cardTotal := len(*cs) for i := cardTotal - 1; i>0; i--{ index := rand.Intn(i+1) (*cs)[i], (*cs)[index] = (*cs)[index], (*cs)[i] } } func (cs *Cards) Reset(){ cardTotal := len(CARDS) *cs = make(Cards, cardTotal) copy(*cs, CARDS) } func (cs* Cards) Len() int{ return len(*cs) } // encode to display func (cs *Cards) Encode() string { encodes := make([]byte, 0, cs.Len()*2) for i := 0; i < cs.Len(); i++ { encodes = append(encodes, cardCode((*cs)[i])...) } return string(encodes) } func cardCode(card int16) []byte { code := make([]byte,2,2) if card <= 0x09 { code[0] = byte(card-0x00) + '0' code[1] = 'p' } else if card <= 0x19 { code[0] = byte(card-0x10) + '0' code[1] = 's' } else if card <= 0x29 { code[0] = byte(card-0x20) + '0' code[1] = 'm' } else if card <= 0x91 { code[0] = byte(card/16)-2 + '0' code[1] = 'z' } else if card <= 0x0105 { code[0] = '0' code[1] = 'p' } else if card <= 0x0115 { code[0] = '0' code[1] = 's' } else if card <= 0x0125 { code[0] = '0' code[1] = 'm' } return code } ``` In this line, the function from math/rand library is used. ```line=13 rand.Seed(time.Now().UnixNano()) ``` which [the package wiki](https://pkg.go.dev/math/rand#Seed) mentioned that `Seed values that have the same remainder when divided by 2^31-1 generate the same pseudo-random sequence.` So rand.Seed(time.Now().UnixNano()) is not really meaningful and the possible combination that the algorithm can generate is only 2^31-1, way less than the possible combination of a wall (which should be greater than 10^120, thx my friend for calculating). I generated the SHA256 hash table and tried to find match from some game recently and also some old games from Aug~Sep 2022, but no hash match was found. <details> <summary>hash table gen code</summary> ```go= package main import ( "fmt" "math/rand" //"time" "bufio" "os" "crypto/sha256" ) func sampleMahjongShuffle() string{ //init seed //rand.Seed(time.Now().UnixNano()) //init cards mahjong := Cards{} mahjong.Reset() //shuffle card mahjong.Shuffle() //print //fmt.Println(mahjong.Encode()) return mahjong.Encode() } var CARDS = []int16{ 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x105, // 0x105 0p 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, // 0x01~0x09 1~9p 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x115, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x125, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x29, 0x31, 0x31, 0x31, 0x31, 0x41, 0x41, 0x41, 0x41, 0x51, 0x51, 0x51, 0x51, 0x61, 0x61, 0x61, 0x61, 0x71, 0x71, 0x71, 0x71, 0x81, 0x81, 0x81, 0x81, 0x91, 0x91, 0x91, 0x91, } type Cards []int16 func (cs *Cards) Shuffle() { cardTotal := len(*cs) for i := cardTotal - 1; i>0; i--{ index := rand.Intn(i+1) (*cs)[i], (*cs)[index] = (*cs)[index], (*cs)[i] } } func (cs *Cards) Reset(){ cardTotal := len(CARDS) *cs = make(Cards, cardTotal) copy(*cs, CARDS) } func (cs* Cards) Len() int{ return len(*cs) } // encode to display func (cs *Cards) Encode() string { encodes := make([]byte, 0, cs.Len()*2) for i := 0; i < cs.Len(); i++ { encodes = append(encodes, cardCode((*cs)[i])...) } return string(encodes) } func cardCode(card int16) []byte { code := make([]byte,2,2) if card <= 0x09 { code[0] = byte(card-0x00) + '0' code[1] = 'p' } else if card <= 0x19 { code[0] = byte(card-0x10) + '0' code[1] = 's' } else if card <= 0x29 { code[0] = byte(card-0x20) + '0' code[1] = 'm' } else if card <= 0x91 { code[0] = byte(card/16)-2 + '0' code[1] = 'z' } else if card <= 0x0105 { code[0] = '0' code[1] = 'p' } else if card <= 0x0115 { code[0] = '0' code[1] = 's' } else if card <= 0x0125 { code[0] = '0' code[1] = 'm' } return code } /// func check(err error) { if err != nil { panic(err) } } func main() { fmt.Println("unoptimized code, performance may be slow. Ran for 800mins on my computer.") fmt.Println("Requires ~140GB space to build table.") fmt.Println("Starting...") for j := 0; j < 215; j++{ filename := fmt.Sprintf("./%03d0000000.txt", j) f, err := os.Create(filename) check(err) for i := 0; i < 10000000; i++ { //init seed //rand.Seed(time.Now().UnixNano()) rand.Seed(int64(j*10000000+i+1)) w := bufio.NewWriter(f) //print SHA256 to file var wall = []byte(sampleMahjongShuffle()) sum :=sha256.Sum256([]byte(wall)) fmt.Fprintf(w, "%x\n", sum) check(err) w.Flush() if (j==214) && (i==7483647) { break } } fmt.Println("completed part ", j) err2 := f.Close() if err2 != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err2) os.Exit(1) } } } ``` </details> I suspected that my code was wrong so I checked it again, but nothing sus found. Something must be wrong ...... ## 2023-02-06 I reported the observation to the developer and the published code is updated. ```go=5 import ( "fmt" "crypto/rand" "math/big" ) ``` ```go=68 func (cs *Cards) Shuffle() { cardTotal := len(*cs) for i := cardTotal - 1; i>0; i--{ index := i randValue, err := rand.Int(rand.Reader, big.NewInt(int64(i+1)) if err == nil { index = int(randValue.Int64()) } (*cs)[i], (*cs)[index] = (*cs)[index], (*cs)[i] } } ```