77 lines
3 KiB
Go
77 lines
3 KiB
Go
package main
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"math/big"
|
||
)
|
||
|
||
// Deck — стопка карт. Индекс 0 — верх колоды (карта, которая уходит первой).
|
||
type Deck struct {
|
||
cards []Card
|
||
}
|
||
|
||
// NewDeck создаёт полную неперетасованную колоду из 52 карт.
|
||
func NewDeck() *Deck {
|
||
d := &Deck{cards: make([]Card, 0, 52)}
|
||
for s := Clubs; s <= Spades; s++ {
|
||
for r := Ace; r <= King; r++ {
|
||
d.cards = append(d.cards, Card{Suit: s, Rank: r})
|
||
}
|
||
}
|
||
return d
|
||
}
|
||
|
||
// Shuffle перемешивает колоду криптографически стойким рандомом
|
||
// (алгоритм Фишера-Йейтса).
|
||
func (d *Deck) Shuffle() {
|
||
for i := len(d.cards) - 1; i > 0; i-- {
|
||
jBig, err := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
|
||
if err != nil {
|
||
// crypto/rand не должен падать в обычных условиях;
|
||
// если это случилось — не имеет смысла продолжать игру.
|
||
panic(err)
|
||
}
|
||
j := int(jBig.Int64())
|
||
d.cards[i], d.cards[j] = d.cards[j], d.cards[i]
|
||
}
|
||
}
|
||
|
||
// Len возвращает количество карт, оставшихся в колоде.
|
||
func (d *Deck) Len() int {
|
||
return len(d.cards)
|
||
}
|
||
|
||
// Draw снимает верхнюю карту колоды. Второе значение — false,
|
||
// если колода пуста.
|
||
func (d *Deck) Draw() (Card, bool) {
|
||
if len(d.cards) == 0 {
|
||
return Card{}, false
|
||
}
|
||
c := d.cards[0]
|
||
d.cards = d.cards[1:]
|
||
return c, true
|
||
}
|
||
|
||
// DrawN снимает n верхних карт (используется при раздаче).
|
||
// Если в колоде меньше карт, чем запрошено, возвращает столько,
|
||
// сколько есть.
|
||
//
|
||
// ВАЖНО: возвращает независимую копию, а не подсрез d.cards. Раньше
|
||
// здесь было "out := d.cards[:n]" — такой подсрез имеет ту же
|
||
// вместимость (cap), что и исходный массив колоды, вплоть до самого
|
||
// его конца. Это означало, что руки РАЗНЫХ игроков после раздачи
|
||
// физически указывали на один и тот же нижележащий массив: как
|
||
// только первый игрок брал карту (append к своей руке), Go писал
|
||
// новый элемент прямо в этот общий массив — по адресу, где у
|
||
// СЛЕДУЮЩЕГО игрока лежала его собственная карта, молча её затирая.
|
||
// Тот самый баг с "одна и та же карта одновременно в сбросе и в
|
||
// комбинации на столе" — именно отсюда.
|
||
func (d *Deck) DrawN(n int) []Card {
|
||
if n > len(d.cards) {
|
||
n = len(d.cards)
|
||
}
|
||
out := make([]Card, n)
|
||
copy(out, d.cards[:n])
|
||
d.cards = d.cards[n:]
|
||
return out
|
||
}
|