guandan.dev

guandan.dev

https://git.tonybtw.com/guandan.dev.git git://git.tonybtw.com/guandan.dev.git
14,523 bytes raw
1
package room
2
3
import (
4
	"time"
5
6
	"guandanbtw/game"
7
	"guandanbtw/protocol"
8
)
9
10
type Play_Action struct {
11
	client   *Client
12
	card_ids []int
13
}
14
15
type Tribute_Action struct {
16
	client  *Client
17
	card_id int
18
}
19
20
type Room struct {
21
	id        string
22
	clients   [4]*Client
23
	game      *game.Game_State
24
	join      chan *Client
25
	leave     chan *Client
26
	play      chan Play_Action
27
	pass      chan *Client
28
	tribute   chan Tribute_Action
29
	fill_bots chan *Client
30
	bot_turn  chan int
31
}
32
33
func new_room(id string) *Room {
34
	return &Room{
35
		id:        id,
36
		join:      make(chan *Client),
37
		leave:    make(chan *Client),
38
		play:      make(chan Play_Action),
39
		pass:      make(chan *Client),
40
		tribute:   make(chan Tribute_Action),
41
		fill_bots: make(chan *Client),
42
		bot_turn:  make(chan int),
43
	}
44
}
45
46
func (r *Room) run() {
47
	for {
48
		select {
49
		case client := <-r.join:
50
			r.handle_join(client)
51
		case client := <-r.leave:
52
			r.handle_leave(client)
53
		case action := <-r.play:
54
			r.handle_play(action)
55
		case client := <-r.pass:
56
			r.handle_pass(client)
57
		case action := <-r.tribute:
58
			r.handle_tribute(action)
59
		case <-r.fill_bots:
60
			r.handle_fill_bots()
61
		case seat := <-r.bot_turn:
62
			r.handle_bot_turn(seat)
63
		}
64
	}
65
}
66
67
func (r *Room) handle_join(client *Client) {
68
	seat := r.find_empty_seat()
69
	if seat == -1 {
70
		client.send_error("room is full")
71
		return
72
	}
73
74
	r.clients[seat] = client
75
	client.room = r
76
77
	r.broadcast_room_state()
78
79
	if r.is_full() {
80
		r.start_game()
81
	}
82
}
83
84
func (r *Room) handle_leave(client *Client) {
85
	for i := range 4 {
86
		if r.clients[i] == client {
87
			r.clients[i] = nil
88
			break
89
		}
90
	}
91
	client.room = nil
92
93
	r.broadcast(&protocol.Message{
94
		Type: protocol.Msg_Player_Left,
95
		Payload: protocol.Player_Info{
96
			Id:   client.id,
97
			Name: client.name,
98
		},
99
	})
100
101
	r.broadcast_room_state()
102
}
103
104
func (r *Room) handle_play(action Play_Action) {
105
	if r.game == nil {
106
		return
107
	}
108
109
	seat := r.get_seat(action.client)
110
	if seat == -1 || seat != r.game.Current_Turn {
111
		action.client.send_error("not your turn")
112
		return
113
	}
114
115
	cards := r.game.Get_Cards_By_Id(seat, action.card_ids)
116
	if cards == nil {
117
		action.client.send_error("invalid cards")
118
		return
119
	}
120
121
	combo := game.Detect_Combination(cards, r.game.Level)
122
	if combo.Type == game.Comb_Invalid {
123
		action.client.send_error("invalid combination")
124
		return
125
	}
126
127
	if r.game.Current_Lead.Type != game.Comb_Invalid {
128
		if !game.Can_Beat(combo, r.game.Current_Lead) {
129
			action.client.send_error("cannot beat current play")
130
			return
131
		}
132
	}
133
134
	r.game.Remove_Cards(seat, action.card_ids)
135
	r.game.Current_Lead = combo
136
	r.game.Lead_Player = seat
137
	r.game.Pass_Count = 0
138
139
	r.broadcast(&protocol.Message{
140
		Type: protocol.Msg_Play_Made,
141
		Payload: protocol.Play_Made_Payload{
142
			Player_Id:  action.client.id,
143
			Seat:       seat,
144
			Cards:      cards,
145
			Combo_Type: combo_type_name(combo.Type),
146
			Is_Pass:    false,
147
		},
148
	})
149
150
	if len(r.game.Hands[seat]) == 0 {
151
		r.game.Finish_Order = append(r.game.Finish_Order, seat)
152
		if r.check_hand_end() {
153
			return
154
		}
155
	}
156
157
	r.advance_turn()
158
}
159
160
func (r *Room) handle_pass(client *Client) {
161
	if r.game == nil {
162
		return
163
	}
164
165
	seat := r.get_seat(client)
166
	if seat == -1 || seat != r.game.Current_Turn {
167
		client.send_error("not your turn")
168
		return
169
	}
170
171
	if r.game.Current_Lead.Type == game.Comb_Invalid {
172
		client.send_error("cannot pass when leading")
173
		return
174
	}
175
176
	r.game.Pass_Count++
177
178
	r.broadcast(&protocol.Message{
179
		Type: protocol.Msg_Play_Made,
180
		Payload: protocol.Play_Made_Payload{
181
			Player_Id: client.id,
182
			Seat:      seat,
183
			Is_Pass:   true,
184
		},
185
	})
186
187
	if r.game.Pass_Count >= 3 {
188
		r.game.Current_Lead = game.Combination{Type: game.Comb_Invalid}
189
		next_leader := r.game.Lead_Player
190
		if r.is_finished(next_leader) {
191
			teammate := (next_leader + 2) % 4
192
			next_leader = teammate
193
		}
194
		r.game.Current_Turn = next_leader
195
		r.game.Pass_Count = 0
196
		r.send_turn_notification()
197
		r.trigger_bot_turn_if_needed()
198
		return
199
	}
200
201
	r.advance_turn()
202
}
203
204
func (r *Room) handle_tribute(action Tribute_Action) {
205
	if r.game == nil || r.game.Phase != game.Phase_Tribute {
206
		return
207
	}
208
209
	seat := r.get_seat(action.client)
210
	tribute_info := r.game.Get_Tribute_Info(seat)
211
	if tribute_info == nil {
212
		action.client.send_error("you don't need to give tribute")
213
		return
214
	}
215
216
	card := r.game.Get_Card_By_Id(seat, action.card_id)
217
	if card == nil {
218
		action.client.send_error("invalid card")
219
		return
220
	}
221
222
	if game.Is_Wild(*card, r.game.Level) {
223
		action.client.send_error("cannot tribute wild cards")
224
		return
225
	}
226
227
	r.game.Remove_Cards(seat, []int{action.card_id})
228
	r.game.Hands[tribute_info.To_Seat] = append(r.game.Hands[tribute_info.To_Seat], *card)
229
230
	if r.clients[tribute_info.To_Seat] != nil {
231
		r.clients[tribute_info.To_Seat].send_message(&protocol.Message{
232
			Type: protocol.Msg_Tribute_Recv,
233
			Payload: protocol.Tribute_Recv_Payload{
234
				Card: *card,
235
			},
236
		})
237
	}
238
239
	r.game.Mark_Tribute_Done(seat)
240
241
	if r.game.All_Tributes_Done() {
242
		r.game.Phase = game.Phase_Play
243
		r.game.Current_Turn = r.game.Tribute_Leader
244
		r.send_turn_notification()
245
		r.trigger_bot_turn_if_needed()
246
	}
247
}
248
249
func (r *Room) start_game() {
250
	r.game = game.New_Game_State()
251
252
	deck := game.New_Deck()
253
	deck.Shuffle()
254
	hands := deck.Deal()
255
256
	for i := 0; i < 4; i++ {
257
		r.game.Hands[i] = hands[i]
258
	}
259
260
	for i := 0; i < 4; i++ {
261
		if r.clients[i] != nil {
262
			r.clients[i].send_message(&protocol.Message{
263
				Type: protocol.Msg_Deal_Cards,
264
				Payload: protocol.Deal_Cards_Payload{
265
					Cards: r.game.Hands[i],
266
					Level: r.game.Level,
267
				},
268
			})
269
		}
270
	}
271
272
	r.game.Phase = game.Phase_Play
273
	r.game.Current_Turn = 0
274
	r.send_turn_notification()
275
	r.trigger_bot_turn_if_needed()
276
}
277
278
func (r *Room) check_hand_end() bool {
279
	if len(r.game.Finish_Order) < 2 {
280
		return false
281
	}
282
283
	first := r.game.Finish_Order[0]
284
	second := r.game.Finish_Order[1]
285
286
	first_team := first % 2
287
	second_team := second % 2
288
289
	if first_team == second_team {
290
		r.end_hand(first_team, r.calculate_level_advance())
291
		return true
292
	}
293
294
	if len(r.game.Finish_Order) >= 3 {
295
		third := r.game.Finish_Order[2]
296
		third_team := third % 2
297
298
		winning_team := first_team
299
		r.end_hand(winning_team, r.calculate_level_advance())
300
		return true
301
		_ = third_team
302
	}
303
304
	return false
305
}
306
307
func (r *Room) calculate_level_advance() int {
308
	if len(r.game.Finish_Order) < 2 {
309
		return 0
310
	}
311
312
	first := r.game.Finish_Order[0]
313
	first_team := first % 2
314
315
	for i, seat := range r.game.Finish_Order {
316
		if seat%2 != first_team {
317
			partner_pos := -1
318
			for j, s := range r.game.Finish_Order {
319
				if s%2 == first_team && j != 0 {
320
					partner_pos = j
321
					break
322
				}
323
			}
324
325
			if partner_pos == -1 {
326
				partner_pos = 3
327
			}
328
329
			switch {
330
			case i == 3 && partner_pos == 1:
331
				return 4
332
			case i == 2 && partner_pos == 1:
333
				return 2
334
			default:
335
				return 1
336
			}
337
		}
338
	}
339
340
	return 4
341
}
342
343
func (r *Room) end_hand(winning_team int, level_advance int) {
344
	old_level := r.game.Team_Levels[winning_team]
345
	new_level := old_level + level_advance
346
	if new_level > 12 {
347
		new_level = 12
348
	}
349
	r.game.Team_Levels[winning_team] = new_level
350
351
	r.broadcast(&protocol.Message{
352
		Type: protocol.Msg_Hand_End,
353
		Payload: protocol.Hand_End_Payload{
354
			Finish_Order:  seats_to_ids(r.game.Finish_Order, r.clients),
355
			Winning_Team:  winning_team,
356
			Level_Advance: level_advance,
357
			New_Levels:    r.game.Team_Levels,
358
		},
359
	})
360
361
	if new_level >= 12 && game.Rank(old_level) == game.Rank_Ace {
362
		r.broadcast(&protocol.Message{
363
			Type: protocol.Msg_Game_End,
364
			Payload: protocol.Game_End_Payload{
365
				Winning_Team: winning_team,
366
				Final_Levels: r.game.Team_Levels,
367
			},
368
		})
369
		return
370
	}
371
372
	r.setup_tribute()
373
}
374
375
func (r *Room) setup_tribute() {
376
	r.game.Setup_Tributes()
377
378
	if len(r.game.Tributes) == 0 {
379
		r.start_new_hand()
380
		return
381
	}
382
383
	for _, t := range r.game.Tributes {
384
		if r.clients[t.From_Seat] != nil {
385
			r.clients[t.From_Seat].send_message(&protocol.Message{
386
				Type: protocol.Msg_Tribute,
387
				Payload: protocol.Tribute_Payload{
388
					From_Seat: t.From_Seat,
389
					To_Seat:   t.To_Seat,
390
				},
391
			})
392
		}
393
	}
394
395
	r.game.Phase = game.Phase_Tribute
396
}
397
398
func (r *Room) start_new_hand() {
399
	r.game.Reset_Hand()
400
401
	deck := game.New_Deck()
402
	deck.Shuffle()
403
	hands := deck.Deal()
404
405
	for i := 0; i < 4; i++ {
406
		r.game.Hands[i] = hands[i]
407
	}
408
409
	for i := 0; i < 4; i++ {
410
		if r.clients[i] != nil {
411
			r.clients[i].send_message(&protocol.Message{
412
				Type: protocol.Msg_Deal_Cards,
413
				Payload: protocol.Deal_Cards_Payload{
414
					Cards: r.game.Hands[i],
415
					Level: r.game.Level,
416
				},
417
			})
418
		}
419
	}
420
421
	r.game.Phase = game.Phase_Play
422
	r.game.Current_Turn = r.game.Tribute_Leader
423
	r.send_turn_notification()
424
	r.trigger_bot_turn_if_needed()
425
}
426
427
func (r *Room) advance_turn() {
428
	for i := 1; i <= 4; i++ {
429
		next := (r.game.Current_Turn + i) % 4
430
		if !r.is_finished(next) {
431
			r.game.Current_Turn = next
432
			r.send_turn_notification()
433
			r.trigger_bot_turn_if_needed()
434
			return
435
		}
436
	}
437
}
438
439
func (r *Room) is_finished(seat int) bool {
440
	for _, s := range r.game.Finish_Order {
441
		if s == seat {
442
			return true
443
		}
444
	}
445
	return false
446
}
447
448
func (r *Room) send_turn_notification() {
449
	can_pass := r.game.Current_Lead.Type != game.Comb_Invalid
450
451
	r.broadcast(&protocol.Message{
452
		Type: protocol.Msg_Turn,
453
		Payload: protocol.Turn_Payload{
454
			Player_Id: r.clients[r.game.Current_Turn].id,
455
			Seat:      r.game.Current_Turn,
456
			Can_Pass:  can_pass,
457
		},
458
	})
459
}
460
461
func (r *Room) broadcast(msg *protocol.Message) {
462
	for _, client := range r.clients {
463
		if client != nil {
464
			client.send_message(msg)
465
		}
466
	}
467
}
468
469
func (r *Room) broadcast_room_state() {
470
	players := make([]protocol.Player_Info, 0)
471
	for i, c := range r.clients {
472
		if c != nil {
473
			players = append(players, protocol.Player_Info{
474
				Id:   c.id,
475
				Name: c.name,
476
				Seat: i,
477
				Team: i % 2,
478
			})
479
		}
480
	}
481
482
	for _, client := range r.clients {
483
		if client != nil {
484
			client.send_message(&protocol.Message{
485
				Type: protocol.Msg_Room_State,
486
				Payload: protocol.Room_State_Payload{
487
					Room_Id:     r.id,
488
					Players:     players,
489
					Game_Active: r.game != nil,
490
					Your_Id:     client.id,
491
				},
492
			})
493
		}
494
	}
495
}
496
497
func (r *Room) find_empty_seat() int {
498
	for i := 0; i < 4; i++ {
499
		if r.clients[i] == nil {
500
			return i
501
		}
502
	}
503
	return -1
504
}
505
506
func (r *Room) is_full() bool {
507
	for _, c := range r.clients {
508
		if c == nil {
509
			return false
510
		}
511
	}
512
	return true
513
}
514
515
func (r *Room) get_seat(client *Client) int {
516
	for i, c := range r.clients {
517
		if c == client {
518
			return i
519
		}
520
	}
521
	return -1
522
}
523
524
func combo_type_name(t game.Combination_Type) string {
525
	names := map[game.Combination_Type]string{
526
		game.Comb_Single:     "single",
527
		game.Comb_Pair:       "pair",
528
		game.Comb_Triple:     "triple",
529
		game.Comb_Full_House: "full_house",
530
		game.Comb_Straight:   "straight",
531
		game.Comb_Tube:       "tube",
532
		game.Comb_Plate:      "plate",
533
		game.Comb_Bomb:       "bomb",
534
	}
535
	return names[t]
536
}
537
538
func seats_to_ids(seats []int, clients [4]*Client) []string {
539
	ids := make([]string, len(seats))
540
	for i, seat := range seats {
541
		if clients[seat] != nil {
542
			ids[i] = clients[seat].id
543
		}
544
	}
545
	return ids
546
}
547
548
func (r *Room) handle_fill_bots() {
549
	bot_idx := 0
550
	for i := 0; i < 4; i++ {
551
		if r.clients[i] == nil && bot_idx < len(bot_names) {
552
			bot := new_bot(generate_id(), bot_names[bot_idx])
553
			bot.room = r
554
			r.clients[i] = bot
555
			bot_idx++
556
		}
557
	}
558
559
	r.broadcast_room_state()
560
561
	if r.is_full() {
562
		r.start_game()
563
	}
564
}
565
566
func (r *Room) handle_bot_turn(seat int) {
567
	if r.game == nil || r.game.Current_Turn != seat {
568
		return
569
	}
570
571
	client := r.clients[seat]
572
	if client == nil || !client.is_bot {
573
		return
574
	}
575
576
	time.Sleep(1500 * time.Millisecond)
577
578
	hand := r.game.Hands[seat]
579
	if len(hand) == 0 {
580
		return
581
	}
582
583
	if r.game.Current_Lead.Type != game.Comb_Invalid {
584
		lead_team := r.game.Lead_Player % 2
585
		bot_team := seat % 2
586
		if lead_team == bot_team {
587
			r.handle_pass(client)
588
			return
589
		}
590
591
		play := r.find_lowest_valid_play(seat)
592
		if play == nil {
593
			r.handle_pass(client)
594
			return
595
		}
596
597
		r.handle_play(Play_Action{
598
			client:   client,
599
			card_ids: play,
600
		})
601
		return
602
	}
603
604
	card_ids := []int{hand[0].Id}
605
	r.handle_play(Play_Action{
606
		client:   client,
607
		card_ids: card_ids,
608
	})
609
}
610
611
func (r *Room) find_lowest_valid_play(seat int) []int {
612
	hand := r.game.Hands[seat]
613
	lead := r.game.Current_Lead
614
615
	sorted_hand := make([]game.Card, len(hand))
616
	copy(sorted_hand, hand)
617
	for i := 0; i < len(sorted_hand)-1; i++ {
618
		for j := i + 1; j < len(sorted_hand); j++ {
619
			vi := game.Card_Value(sorted_hand[i], r.game.Level)
620
			vj := game.Card_Value(sorted_hand[j], r.game.Level)
621
			if vi > vj {
622
				sorted_hand[i], sorted_hand[j] = sorted_hand[j], sorted_hand[i]
623
			}
624
		}
625
	}
626
627
	if lead.Type == game.Comb_Single {
628
		for _, card := range sorted_hand {
629
			combo := game.Detect_Combination([]game.Card{card}, r.game.Level)
630
			if game.Can_Beat(combo, lead) {
631
				return []int{card.Id}
632
			}
633
		}
634
	}
635
636
	if lead.Type == game.Comb_Pair {
637
		rank_cards := make(map[game.Rank][]game.Card)
638
		for _, card := range sorted_hand {
639
			rank_cards[card.Rank] = append(rank_cards[card.Rank], card)
640
		}
641
642
		var valid_pairs [][]game.Card
643
		for _, cards := range rank_cards {
644
			if len(cards) >= 2 {
645
				combo := game.Detect_Combination(cards[:2], r.game.Level)
646
				if game.Can_Beat(combo, lead) {
647
					valid_pairs = append(valid_pairs, cards[:2])
648
				}
649
			}
650
		}
651
652
		if len(valid_pairs) > 0 {
653
			lowest := valid_pairs[0]
654
			lowest_val := game.Card_Value(lowest[0], r.game.Level)
655
			for _, pair := range valid_pairs[1:] {
656
				val := game.Card_Value(pair[0], r.game.Level)
657
				if val < lowest_val {
658
					lowest = pair
659
					lowest_val = val
660
				}
661
			}
662
			return []int{lowest[0].Id, lowest[1].Id}
663
		}
664
	}
665
666
	if lead.Type == game.Comb_Triple {
667
		rank_cards := make(map[game.Rank][]game.Card)
668
		for _, card := range sorted_hand {
669
			rank_cards[card.Rank] = append(rank_cards[card.Rank], card)
670
		}
671
672
		var valid_triples [][]game.Card
673
		for _, cards := range rank_cards {
674
			if len(cards) >= 3 {
675
				combo := game.Detect_Combination(cards[:3], r.game.Level)
676
				if game.Can_Beat(combo, lead) {
677
					valid_triples = append(valid_triples, cards[:3])
678
				}
679
			}
680
		}
681
682
		if len(valid_triples) > 0 {
683
			lowest := valid_triples[0]
684
			lowest_val := game.Card_Value(lowest[0], r.game.Level)
685
			for _, triple := range valid_triples[1:] {
686
				val := game.Card_Value(triple[0], r.game.Level)
687
				if val < lowest_val {
688
					lowest = triple
689
					lowest_val = val
690
				}
691
			}
692
			return []int{lowest[0].Id, lowest[1].Id, lowest[2].Id}
693
		}
694
	}
695
696
	return nil
697
}
698
699
func (r *Room) trigger_bot_turn_if_needed() {
700
	if r.game == nil {
701
		return
702
	}
703
704
	seat := r.game.Current_Turn
705
	client := r.clients[seat]
706
	if client != nil && client.is_bot {
707
		go func() {
708
			r.bot_turn <- seat
709
		}()
710
	}
711
}