board.go

  1package game
  2
  3import (
  4	"github.com/zikaeroh/codies/internal/words"
  5)
  6
  7// Team number, starting at zero.
  8type Team int
  9
 10func (t Team) next(numTeams int) Team {
 11	return (t + 1) % Team(numTeams)
 12}
 13
 14type Tile struct {
 15	// Immutable
 16	Word    string
 17	Team    Team
 18	Neutral bool
 19	Bomb    bool
 20
 21	// Mutable
 22	Revealed bool
 23}
 24
 25type Board struct {
 26	Rows, Cols int
 27	WordCounts []int
 28	tiles      []*Tile // len(items)=rows*cols, access via items[row*rows + col]
 29}
 30
 31func newBoard(rows, cols int, words words.List, startingTeam Team, numTeams int, rand Rand) *Board {
 32	if startingTeam < 0 || int(startingTeam) >= numTeams {
 33		panic("invalid starting team")
 34	}
 35
 36	n := rows * cols
 37	layout, ok := layouts[layoutKey{boardSize: n, numTeams: numTeams}]
 38	if !ok {
 39		panic("invalid board dimension")
 40	}
 41
 42	// Copy and rotate teams to give the first team the most words.
 43	old := layout.teams
 44	layout.teams = append([]int(nil), old[startingTeam:]...)
 45	layout.teams = append(layout.teams, old[:startingTeam]...)
 46	wordCounts := append([]int(nil), layout.teams...)
 47
 48	items := make([]*Tile, n)
 49	seen := make(map[int]struct{}, n)
 50
 51	for i := range items {
 52		var w string
 53		for {
 54			j := rand.Intn(words.Len())
 55			if _, ok := seen[j]; !ok {
 56				seen[j] = struct{}{}
 57				w = words.Get(j)
 58				break
 59			}
 60		}
 61
 62		item := &Tile{Word: w}
 63
 64	ItemSwitch:
 65		switch {
 66		case layout.bomb > 0:
 67			layout.bomb--
 68			item.Bomb = true
 69
 70		case layout.neutral > 0:
 71			layout.neutral--
 72			item.Neutral = true
 73
 74		default:
 75			for t, c := range layout.teams {
 76				if c == 0 {
 77					continue
 78				}
 79
 80				layout.teams[t]--
 81				item.Team = Team(t)
 82				break ItemSwitch
 83			}
 84
 85			panic("unreachable")
 86		}
 87
 88		items[i] = item
 89	}
 90
 91	rand.Shuffle(len(items), func(i, j int) {
 92		items[i], items[j] = items[j], items[i]
 93	})
 94
 95	return &Board{
 96		Rows:       rows,
 97		Cols:       cols,
 98		WordCounts: wordCounts,
 99		tiles:      items,
100	}
101}
102
103func (b *Board) Get(row, col int) *Tile {
104	switch {
105	case row < 0:
106	case col < 0:
107	case row >= b.Rows:
108	case col >= b.Cols:
109	default:
110		i := row*b.Rows + col
111		return b.tiles[i]
112	}
113
114	return nil
115}