0x4a52466c696e74 1 month ago
commit
835eed6c5a
7 changed files with 355 additions and 0 deletions
  1. 43 0
      card.go
  2. 143 0
      deck.go
  3. 3 0
      go.mod
  4. 64 0
      number.go
  5. 54 0
      suit.go
  6. 12 0
      tools.go
  7. 36 0
      z_test.go

+ 43 - 0
card.go

@@ -0,0 +1,43 @@
+package cards
+
+import "fmt"
+
+// карта
+type Card struct {
+	N Number
+	S Suit
+}
+
+// проверка, корректно ли объявлена карта
+func (s *Card) IsValid() bool {
+	// масть у джокера отсутствует
+	if s.N == Joker {
+		return true
+	}
+	// для остальных карт, кроме джокера, проверяем корректно объявленные порядок и масть
+	return s.N.IsValid() && s.S.IsValid()
+}
+
+// полное наименование карты
+func (s *Card) Name() string {
+	return fmt.Sprintf("%s %s", s.N.Name(), s.S.Name())
+}
+
+// краткое наименование карты
+func (s *Card) NameShort() string {
+	// XX выводим, если карта объявлена некорректно
+	if !s.IsValid() {
+		return "XX"
+	}
+	// джокер. выводим только порядок
+	if !s.S.IsValid() {
+		return s.N.NameShort()
+	}
+	// для остальных карт кроме джокера выводим порядок и масть
+	return fmt.Sprintf("%s%s", s.N.NameShort(), s.S.NameShort())
+}
+
+// строковое представление карты
+func (s *Card) String() string {
+	return s.NameShort()
+}

+ 143 - 0
deck.go

@@ -0,0 +1,143 @@
+package cards
+
+import (
+	cryptoRand "crypto/rand"
+	"fmt"
+	"math/big"
+)
+
+// новая колода из 36 карт. jokers - количество джокеров в колоде
+func NewDeck36(jokers int) *Deck {
+	deck, _ := NewDeckOffset(Number(6), jokers)
+	return deck
+}
+
+// новая колода из 54 карт. jokers - количество джокеров в колоде
+func NewDeck54(jokers int) *Deck {
+	deck, _ := NewDeckOffset(Number(2), jokers)
+	return deck
+}
+
+// новая колода. 4 масти, порядок начинается с offset (минимальный)
+func NewDeckOffset(offset Number, jockers int) (*Deck, error) {
+	if !offset.IsValid() {
+		return nil, fmt.Errorf("invalid offset: %d", offset)
+	}
+	cards := make([]*Card, 0, 52+jockers)
+	for i := Hearts; i <= Spades; i++ {
+		for j := Number(2); j <= Ace; j++ {
+			cards = append(
+				cards,
+				&Card{
+					N: j,
+					S: i,
+				},
+			)
+		}
+	}
+	for i := 0; i < jockers; i++ {
+		cards = append(
+			cards,
+			&Card{
+				N: Joker,
+				S: Undefined,
+			})
+	}
+	deck := &Deck{
+		countJokers: jockers,
+		cards:       cards,
+	}
+	return deck, nil
+}
+
+// колода
+type Deck struct {
+	countJokers int
+	cards       []*Card
+	offsetLeft  int
+	offsetRight int
+}
+
+// количество джокеров в колоде
+func (s *Deck) CountJokers() int {
+	return s.countJokers
+}
+
+// собираем колоду в исходное состояние (возвращаем вышедшие карты)
+func (s *Deck) ResetOffset() {
+	s.offsetLeft = 0
+	s.offsetRight = 0
+}
+
+// количество карт в колоде
+func (s *Deck) Count() int {
+	return len(s.cards)
+}
+
+// количество карт без джокеров
+func (s *Deck) CountWithoutJokers() int {
+	return len(s.cards) - s.countJokers
+}
+
+// количество оставшихся карт в колоде
+func (s *Deck) CountLeft() int {
+	return len(s.cards) - s.offsetLeft - s.offsetRight
+}
+
+// переместить карту с позиции pos на позицию newPos
+// если pos или newPos выходят за рамки массива колоды, результат будет false
+func (s *Deck) MoveCard(pos, newPos int) bool {
+	if (pos < 0 || pos >= len(s.cards)) || (newPos < 0 || newPos >= len(s.cards)) {
+		return false
+	}
+	s.cards[pos], s.cards[newPos] = s.cards[newPos], s.cards[pos]
+	return true
+}
+
+// перемешать колоду (стандартныйметод с использованием криптографически стойкого генератора)
+func (s *Deck) Shuffle() {
+	n := len(s.cards)
+	for i := range s.cards {
+		// генерация случайного индекса с использованием криптографически стойкого генератора
+		jBig, _ := cryptoRand.Int(cryptoRand.Reader, big.NewInt(int64(n-i)))
+		j := int(jBig.Int64()) + i
+		// меняем позицию
+		s.cards[i], s.cards[j] = s.cards[j], s.cards[i]
+	}
+}
+
+// взять карту снизу
+func (s *Deck) PopLeft() (*Card, bool) {
+	if s.CountLeft() == 0 {
+		return nil, false
+	}
+	card := s.cards[s.offsetLeft]
+	s.offsetLeft += 1
+	return card, true
+}
+
+// взять карту сверху
+func (s *Deck) PopRight() (*Card, bool) {
+	if s.CountLeft() == 0 {
+		return nil, false
+	}
+	card := s.cards[len(s.cards)-s.offsetRight-1]
+	s.offsetRight += 1
+	return card, true
+}
+
+// строка полной колоды (включая вышедшие карты)
+func (s *Deck) StringAll() string {
+	return showCardSlice(s.cards)
+}
+
+// строка только оставшихся карт
+func (s *Deck) StringLeft() (res string) {
+	left := s.cards[s.offsetLeft : len(s.cards)-s.offsetRight]
+	return showCardSlice(left)
+}
+
+// строка только вышедших карт
+func (s *Deck) String() string {
+	return "[ " + s.StringLeft() + " ]"
+}

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module git.ali33.ru/fcg-xvii/cards
+
+go 1.23.1

+ 64 - 0
number.go

@@ -0,0 +1,64 @@
+package cards
+
+import "fmt"
+
+const (
+	Jack  Number = 11
+	Queen Number = 12
+	King  Number = 13
+	Ace   Number = 14
+	Joker Number = 15
+)
+
+// порядок карты
+type Number byte
+
+// конвертация в строку
+func (s Number) String() string {
+	return s.NameShort()
+}
+
+// полное наименование порядка
+func (s Number) Name() string {
+	switch s {
+	case 2, 3, 4, 5, 6, 7, 8, 9, 10:
+		return fmt.Sprint(int(s))
+	case Jack:
+		return "Jack"
+	case Queen:
+		return "Queen"
+	case King:
+		return "King"
+	case Ace:
+		return "Ace"
+	case Joker:
+		return "Joker"
+	default:
+		return "Undefined"
+	}
+}
+
+// краткое наименование порядка
+func (s Number) NameShort() string {
+	switch s {
+	case 2, 3, 4, 5, 6, 7, 8, 9, 10:
+		return fmt.Sprint(int(s))
+	case Jack:
+		return "J"
+	case Queen:
+		return "Q"
+	case King:
+		return "K"
+	case Ace:
+		return "A"
+	case Joker:
+		return "JR"
+	default:
+		return ""
+	}
+}
+
+// проверка, корректно ли объявлен порядок
+func (s Number) IsValid() bool {
+	return s >= 2 && s <= Joker
+}

+ 54 - 0
suit.go

@@ -0,0 +1,54 @@
+package cards
+
+const (
+	// червы
+	Hearts Suit = iota
+	// бубны
+	Diamonds
+	// крести
+	Clubs
+	// пики
+	Spades
+	// значение не определено (корректно только для джокера)
+	Undefined
+)
+
+// масть карты
+type Suit byte
+
+// полное наименование масти
+func (s Suit) Name() string {
+	switch s {
+	case Hearts:
+		return "Hearts"
+	case Diamonds:
+		return "Diamonds"
+	case Clubs:
+		return "Clubs"
+	case Spades:
+		return "Spades"
+	default:
+		return "Undefined"
+	}
+}
+
+// краткое наименование масти
+func (s Suit) NameShort() string {
+	switch s {
+	case Hearts:
+		return "♥"
+	case Diamonds:
+		return "♦"
+	case Clubs:
+		return "♣"
+	case Spades:
+		return "♠"
+	default:
+		return ""
+	}
+}
+
+// проверка, корректно ли объявлена масть
+func (s Suit) IsValid() bool {
+	return s <= Spades
+}

+ 12 - 0
tools.go

@@ -0,0 +1,12 @@
+package cards
+
+// конвертация массива карт в строку
+func showCardSlice(sl []*Card) (res string) {
+	for i, card := range sl {
+		res += card.NameShort()
+		if i < len(sl)-1 {
+			res += " "
+		}
+	}
+	return
+}

+ 36 - 0
z_test.go

@@ -0,0 +1,36 @@
+package cards_test
+
+import (
+	"log"
+	"testing"
+
+	"git.ali33.ru/fcg-xvii/cards"
+)
+
+func deckOffset(deck *cards.Deck) {
+	log.Println(deck)
+	deck.Shuffle()
+	log.Println(deck)
+	c, check := deck.PopRight()
+	for check {
+		log.Println(c, check)
+		c, check = deck.PopRight()
+	}
+	deck.ResetOffset()
+	deck.Shuffle()
+	c, check = deck.PopLeft()
+	for check {
+		log.Println(c, check)
+		c, check = deck.PopLeft()
+	}
+}
+
+func TestDeck32(t *testing.T) {
+	deck := cards.NewDeck36(2)
+	deckOffset(deck)
+}
+
+func TestDeck54(t *testing.T) {
+	deck := cards.NewDeck54(2)
+	deckOffset(deck)
+}