room.go

  1package game
  2
  3import (
  4	"github.com/gofrs/uuid"
  5	"github.com/zikaeroh/codies/internal/words"
  6	"github.com/zikaeroh/codies/internal/words/static"
  7)
  8
  9type PlayerID = uuid.UUID
 10
 11type WordList struct {
 12	Name   string
 13	Custom bool
 14	List   words.List
 15
 16	Enabled bool
 17}
 18
 19func defaultWords() []*WordList {
 20	return []*WordList{
 21		{
 22			Name:    "Base",
 23			List:    static.Default,
 24			Enabled: true,
 25		},
 26		{
 27			Name: "Duet",
 28			List: static.Duet,
 29		},
 30		{
 31			Name: "Undercover",
 32			List: static.Undercover,
 33		},
 34	}
 35}
 36
 37type Room struct {
 38	rand Rand
 39
 40	// Configuration for the next new game.
 41	Rows, Cols int
 42
 43	Version   int
 44	Board     *Board
 45	Turn      Team
 46	Winner    *Team
 47	Players   map[PlayerID]*Player
 48	Teams     [][]PlayerID // To preserve the ordering of teams.
 49	WordLists []*WordList
 50}
 51
 52func NewRoom(rand Rand) *Room {
 53	if rand == nil {
 54		rand = globalRand{}
 55	}
 56
 57	return &Room{
 58		rand:      rand,
 59		Rows:      5,
 60		Cols:      5,
 61		Players:   make(map[PlayerID]*Player),
 62		Teams:     make([][]PlayerID, 2), // TODO: support more than 2 teams
 63		WordLists: defaultWords(),
 64	}
 65}
 66
 67type Player struct {
 68	ID        PlayerID
 69	Nickname  string
 70	Team      Team
 71	Spymaster bool
 72}
 73
 74func (r *Room) AddPlayer(id PlayerID, nickname string) {
 75	if p, ok := r.Players[id]; ok {
 76		if p.Nickname == nickname {
 77			return
 78		}
 79
 80		p.Nickname = nickname
 81		r.Version++
 82		return
 83	}
 84
 85	team := r.smallestTeam()
 86	p := &Player{
 87		ID:       id,
 88		Nickname: nickname,
 89		Team:     team,
 90	}
 91
 92	r.Players[id] = p
 93	r.Teams[team] = append(r.Teams[team], id)
 94	r.Version++
 95}
 96
 97func (r *Room) smallestTeam() Team {
 98	min := Team(0)
 99	minLen := len(r.Teams[0])
100
101	for tInt, team := range r.Teams {
102		if len(team) < minLen {
103			min = Team(tInt)
104			minLen = len(team)
105		}
106	}
107
108	return min
109}
110
111func (r *Room) words() (list words.List) {
112	for _, w := range r.WordLists {
113		if w.Enabled {
114			list = list.Concat(w.List)
115		}
116	}
117	return list
118}
119
120func (r *Room) NewGame() {
121	words := r.words()
122
123	if r.Rows*r.Cols > words.Len() {
124		panic("not enough words")
125	}
126
127	r.Version++
128	r.Winner = nil
129	r.Turn = Team(r.rand.Intn(len(r.Teams)))
130	r.Board = newBoard(r.Rows, r.Cols, words, r.Turn, len(r.Teams), r.rand)
131}
132
133func (r *Room) EndTurn(id PlayerID) {
134	if r.Winner != nil {
135		return
136	}
137
138	p := r.Players[id]
139	if p == nil {
140		return
141	}
142
143	if p.Team != r.Turn || p.Spymaster {
144		return
145	}
146
147	r.ForceEndTurn()
148}
149
150func (r *Room) nextTeam() Team {
151	return r.Turn.next(len(r.Teams))
152}
153
154func (r *Room) nextTurn() {
155	r.Turn = r.nextTeam()
156}
157
158func (r *Room) ForceEndTurn() {
159	r.Version++
160	r.nextTurn()
161}
162
163func (r *Room) RemovePlayer(id PlayerID) {
164	p := r.Players[id]
165	if p == nil {
166		return
167	}
168
169	r.Version++
170	delete(r.Players, id)
171
172	r.Teams[p.Team] = removePlayer(r.Teams[p.Team], id)
173}
174
175func (r *Room) Reveal(id PlayerID, row, col int) {
176	if r.Winner != nil {
177		return
178	}
179
180	p := r.Players[id]
181	if p == nil {
182		return
183	}
184
185	if p.Spymaster || p.Team != r.Turn {
186		return
187	}
188
189	tile := r.Board.Get(row, col)
190	if tile == nil {
191		return
192	}
193
194	if tile.Revealed {
195		return
196	}
197
198	tile.Revealed = true
199
200	switch {
201	case tile.Neutral:
202		r.nextTurn()
203	case tile.Bomb:
204		// TODO: Who wins when there's more than one team?
205		// Maybe eliminate the team who clicked?
206		winner := r.nextTeam()
207		r.Winner = &winner
208	default:
209		r.Board.WordCounts[tile.Team]--
210		if r.Board.WordCounts[tile.Team] == 0 {
211			winner := tile.Team
212			r.Winner = &winner
213		} else if tile.Team != p.Team {
214			r.nextTurn()
215		}
216	}
217
218	r.Version++
219}
220
221func (r *Room) ChangeRole(id PlayerID, spymaster bool) {
222	if r.Winner != nil {
223		return
224	}
225
226	p := r.Players[id]
227	if p == nil {
228		return
229	}
230
231	if p.Spymaster == spymaster {
232		return
233	}
234
235	p.Spymaster = spymaster
236	r.Version++
237}
238
239func (r *Room) ChangeTeam(id PlayerID, team Team) {
240	if team < 0 || int(team) >= len(r.Teams) {
241		return
242	}
243
244	p := r.Players[id]
245	if p == nil {
246		return
247	}
248
249	if p.Team == team {
250		return
251	}
252
253	r.Teams[p.Team] = removePlayer(r.Teams[p.Team], id)
254	r.Teams[team] = append(r.Teams[team], id)
255	p.Team = team
256	r.Version++
257}
258
259func removePlayer(team []PlayerID, remove PlayerID) []PlayerID {
260	newTeam := make([]PlayerID, 0, len(team)-1)
261	for _, id := range team {
262		if id != remove {
263			newTeam = append(newTeam, id)
264		}
265	}
266	return newTeam
267}
268
269func (r *Room) RandomizeTeams() {
270	players := make([]PlayerID, 0, len(r.Players))
271	for id := range r.Players {
272		players = append(players, id)
273	}
274
275	r.rand.Shuffle(len(players), func(i, j int) {
276		players[i], players[j] = players[j], players[i]
277	})
278
279	numTeams := len(r.Teams)
280	newTeams := make([][]PlayerID, numTeams)
281	for i := range newTeams {
282		newTeams[i] = make([]PlayerID, 0, len(players)/numTeams)
283	}
284
285	for i, id := range players {
286		team := i % numTeams
287		newTeams[team] = append(newTeams[team], id)
288	}
289
290	r.rand.Shuffle(numTeams, func(i, j int) {
291		newTeams[i], newTeams[j] = newTeams[j], newTeams[i]
292	})
293
294	for team, players := range newTeams {
295		for _, id := range players {
296			r.Players[id].Team = Team(team)
297		}
298	}
299
300	r.Teams = newTeams
301	r.Version++
302}
303
304func (r *Room) ChangePack(num int, enable bool) {
305	if num < 0 || num >= len(r.WordLists) {
306		return
307	}
308
309	pack := r.WordLists[num]
310
311	if pack.Enabled == enable {
312		return
313	}
314
315	if !enable {
316		total := 0
317		for _, p := range r.WordLists {
318			if p.Enabled {
319				total++
320			}
321		}
322
323		if total < 2 {
324			return
325		}
326	}
327
328	pack.Enabled = enable
329	r.Version++
330}
331
332func (r *Room) AddPack(name string, wds []string) {
333	if len(r.WordLists) >= 10 {
334		return
335	}
336
337	list := &WordList{
338		Name:   name,
339		Custom: true,
340		List:   words.NewList(wds),
341	}
342	r.WordLists = append(r.WordLists, list)
343	r.Version++
344}
345
346func (r *Room) RemovePack(num int) {
347	if num < 0 || num >= len(r.WordLists) {
348		return
349	}
350
351	if pack := r.WordLists[num]; !pack.Custom || pack.Enabled {
352		return
353	}
354
355	// https://github.com/golang/go/wiki/SliceTricks
356	lists := r.WordLists
357	copy(lists[num:], lists[num+1:])
358	lists[len(lists)-1] = nil
359	lists = lists[:len(lists)-1]
360	r.WordLists = lists
361
362	r.Version++
363}