# 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]
}
}
```