room.go

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