Initial commit
This commit is contained in:
commit
08e4d196bf
12 changed files with 1361 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.idea/
|
||||
dist/
|
||||
vendor/
|
||||
*.sh
|
||||
jack
|
26
.gitlab-ci.yml
Normal file
26
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,26 @@
|
|||
image: golang:latest
|
||||
|
||||
stages:
|
||||
- validate
|
||||
- build
|
||||
|
||||
fmt:
|
||||
stage: validate
|
||||
script:
|
||||
- gofmt -l -s -e .
|
||||
- exit $(gofmt -l -s -e . | wc -l)
|
||||
|
||||
vet:
|
||||
stage: validate
|
||||
script:
|
||||
- go vet -composites=false ./...
|
||||
|
||||
test:
|
||||
stage: validate
|
||||
script:
|
||||
- go test -race -v ./...
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- go build
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Trevor Slocum <trevor@rocketnine.space>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
13
README.md
Normal file
13
README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# jack
|
||||
[![CI status](https://gitlab.com/tslocum/jack/badges/master/pipeline.svg)](https://gitlab.com/tslocum/jack/commits/master)
|
||||
[![Donate](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space)
|
||||
|
||||
Cribbage server
|
||||
|
||||
## Demo
|
||||
|
||||
[Cribbage.World](https://cribbage.world) is powered by jack.
|
||||
|
||||
## Support
|
||||
|
||||
Please share issues/suggestions [here](https://gitlab.com/tslocum/jack/issues).
|
60
card.go
Normal file
60
card.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitlab.com/tslocum/joker"
|
||||
)
|
||||
|
||||
type PlayerCard struct {
|
||||
joker.Card
|
||||
Player int
|
||||
}
|
||||
|
||||
func (c PlayerCard) String() string {
|
||||
return fmt.Sprintf("{%d}%s", c.Player, c.Card)
|
||||
}
|
||||
|
||||
type PlayerCards []PlayerCard
|
||||
|
||||
func (c PlayerCards) String() string {
|
||||
var s strings.Builder
|
||||
for i := range c {
|
||||
if i > 0 {
|
||||
s.WriteRune(',')
|
||||
}
|
||||
s.WriteString(c[i].String())
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (c PlayerCards) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c PlayerCards) Less(i, j int) bool {
|
||||
return c[i].Value() < c[j].Value()
|
||||
}
|
||||
|
||||
func (c PlayerCards) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
func (c PlayerCards) Cards() joker.Cards {
|
||||
var cards = make(joker.Cards, len(c))
|
||||
for i, card := range c {
|
||||
cards[i] = card.Card
|
||||
}
|
||||
return cards
|
||||
}
|
||||
|
||||
func (c PlayerCards) PlayerCards(player int) int {
|
||||
var i int
|
||||
for _, card := range c {
|
||||
if card.Player == player {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
190
client.go
Normal file
190
client.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"gitlab.com/tslocum/joker"
|
||||
)
|
||||
|
||||
const ClientTerminating = -1
|
||||
const ClientTelnet = 1
|
||||
const ClientWebsocket = 2
|
||||
|
||||
type Client struct {
|
||||
ConnType int
|
||||
ConnTelnet net.Conn
|
||||
ConnWebsocket *websocket.Conn
|
||||
game *Game
|
||||
gameplayer int
|
||||
gameready bool
|
||||
gamewaiting string
|
||||
gamego bool
|
||||
readbuffer chan string
|
||||
writebuffer chan string
|
||||
}
|
||||
|
||||
func (c *Client) initialize() {
|
||||
c.readbuffer = make(chan string, 10)
|
||||
c.writebuffer = make(chan string, 10)
|
||||
}
|
||||
|
||||
func (c *Client) processRead() {
|
||||
var command_pieces []string
|
||||
var handled bool
|
||||
for command := range c.readbuffer {
|
||||
if command == "" || c.game == nil {
|
||||
break
|
||||
} else if c.ConnType == ClientTerminating {
|
||||
continue
|
||||
}
|
||||
handled = false
|
||||
|
||||
command = strings.TrimLeft(command, " \r\n"+string(0))
|
||||
command = strings.TrimRight(command, " \r\n"+string(0))
|
||||
command_pieces = strings.Fields(strings.ToLower(command))
|
||||
if len(command_pieces) > 0 {
|
||||
handled = true
|
||||
|
||||
args := ""
|
||||
if len(command_pieces) > 1 {
|
||||
for i := 1; i < len(command_pieces); i++ {
|
||||
command_piece := command_pieces[i]
|
||||
command_piece = strings.TrimLeft(command_piece, " \r\n"+string(0))
|
||||
command_piece = strings.TrimRight(command_piece, " \r\n"+string(0))
|
||||
|
||||
if command_piece != "" {
|
||||
if args != "" {
|
||||
args += " "
|
||||
}
|
||||
args += command_piece
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
card, sentCardIdentifier := joker.Parse(command_pieces[0])
|
||||
|
||||
if _, err := strconv.Atoi(command_pieces[0]); err == nil {
|
||||
c.game.CommandQueue <- GameCommand{Player: c.gameplayer, Command: CommandRaw, Value: command_pieces[0] + " " + args}
|
||||
} else if sentCardIdentifier && c.game.getHand(c.gameplayer).Contains(card) {
|
||||
c.game.CommandQueue <- GameCommand{Player: c.gameplayer, Command: CommandRaw, Value: command_pieces[0] + " " + args}
|
||||
} else if command_pieces[0] == "continue" {
|
||||
c.game.CommandQueue <- GameCommand{Player: c.gameplayer, Command: CommandContinue, Value: args}
|
||||
} else if command_pieces[0] == "cut" || command_pieces[0] == "c" || command_pieces[0] == "cu" {
|
||||
c.game.CommandQueue <- GameCommand{Player: c.gameplayer, Command: CommandCut, Value: args}
|
||||
} else if command_pieces[0] == "throw" || command_pieces[0] == "t" || command_pieces[0] == "th" {
|
||||
c.game.CommandQueue <- GameCommand{Player: c.gameplayer, Command: CommandThrow, Value: args}
|
||||
} else if command_pieces[0] == "print" && c.game != nil {
|
||||
c.game.printAll()
|
||||
} else {
|
||||
handled = false
|
||||
}
|
||||
}
|
||||
|
||||
if !handled {
|
||||
c.write(c.game.printGame(c.gameplayer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) read(message string) {
|
||||
if c.ConnType == ClientTerminating {
|
||||
return
|
||||
}
|
||||
|
||||
c.readbuffer <- message
|
||||
}
|
||||
|
||||
func (c *Client) handleRead() {
|
||||
if c.ConnType == ClientWebsocket {
|
||||
for {
|
||||
_, message, err := c.ConnWebsocket.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("WebSocket read error:", err)
|
||||
break
|
||||
}
|
||||
|
||||
log.Println("WebSocket read:", string(message))
|
||||
c.readbuffer <- string(message)
|
||||
}
|
||||
} else {
|
||||
buf := make([]byte, 4096)
|
||||
var readtext string
|
||||
var nextline int
|
||||
for {
|
||||
n, err := c.ConnTelnet.Read(buf)
|
||||
if err != nil || n == 0 {
|
||||
c.ConnTelnet.Close()
|
||||
|
||||
break
|
||||
}
|
||||
readtext += strings.TrimLeft(string(buf), "\r\n"+string(0))
|
||||
for {
|
||||
readtext = strings.TrimLeft(readtext, "\r\n"+string(0))
|
||||
nextline = strings.Index(readtext, "\n")
|
||||
if nextline <= 0 {
|
||||
if readtext != "" {
|
||||
c.read(readtext)
|
||||
readtext = ""
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
c.read(readtext[:nextline+1])
|
||||
readtext = readtext[nextline+1:]
|
||||
}
|
||||
buf = make([]byte, 4096)
|
||||
}
|
||||
log.Printf("Telnet connection closed")
|
||||
}
|
||||
|
||||
c.terminate()
|
||||
|
||||
if c.game != nil && c.game.Phase != PhaseEnd {
|
||||
c.game.end(c.game.getOpponent(c.gameplayer), "Player disconnected")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) terminate() {
|
||||
c.write("")
|
||||
c.ConnType = ClientTerminating
|
||||
c.readbuffer <- ""
|
||||
}
|
||||
|
||||
func (c *Client) write(message string) {
|
||||
if c.ConnType == ClientTerminating {
|
||||
return
|
||||
}
|
||||
|
||||
c.writebuffer <- message
|
||||
}
|
||||
|
||||
func (c *Client) handleWrite() {
|
||||
for message := range c.writebuffer {
|
||||
if message == "" {
|
||||
break
|
||||
} else if c.ConnType == ClientTerminating {
|
||||
continue
|
||||
}
|
||||
|
||||
if c.ConnType == ClientWebsocket {
|
||||
err := c.ConnWebsocket.WriteMessage(websocket.TextMessage, []byte(message))
|
||||
if err != nil {
|
||||
log.Printf("WebSocket connection closed: %v", err)
|
||||
|
||||
break
|
||||
}
|
||||
} else {
|
||||
_, err := c.ConnTelnet.Write([]byte(message + "\n\n"))
|
||||
if err != nil {
|
||||
c.ConnTelnet.Close()
|
||||
log.Printf("Telnet connection closed: %v", err)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
867
game.go
Normal file
867
game.go
Normal file
|
@ -0,0 +1,867 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitlab.com/tslocum/joker"
|
||||
. "gitlab.com/tslocum/joker"
|
||||
cribbage "gitlab.com/tslocum/joker-cribbage"
|
||||
)
|
||||
|
||||
const (
|
||||
PhaseEnd = -1
|
||||
PhaseSetup = 0
|
||||
PhasePick = 1
|
||||
PhasePeg = 2
|
||||
PhaseScore = 3
|
||||
)
|
||||
|
||||
const (
|
||||
PegPhaseNormal = 0
|
||||
PegPhaseSolo = 1
|
||||
PegPhaseFinal = 2
|
||||
)
|
||||
|
||||
const WinningScore = 121
|
||||
|
||||
type Game struct {
|
||||
CommandQueue chan GameCommand `json:"-"`
|
||||
|
||||
Deck *Deck `json:"-"`
|
||||
Phase int `json:"phase"`
|
||||
Dealer int `json:"dealer"`
|
||||
Turn int `json:"turn"`
|
||||
Starter Card `json:"starter"`
|
||||
|
||||
Player1 *Client `json:"-"`
|
||||
Player2 *Client `json:"-"`
|
||||
Hand1 Cards `json:"hand1"`
|
||||
Hand2 Cards `json:"hand2"`
|
||||
Crib PlayerCards `json:"crib"`
|
||||
Score1 int `json:"score1"`
|
||||
Score2 int `json:"score2"`
|
||||
|
||||
ThrowPile PlayerCards `json:"throwpile"`
|
||||
DiscardPile PlayerCards `json:"-"`
|
||||
|
||||
PegPhase int `json:"-"`
|
||||
sync.RWMutex `json:"-"`
|
||||
}
|
||||
|
||||
func NewGame(player1 *Client, player2 *Client) *Game {
|
||||
game := new(Game)
|
||||
game.Initialize()
|
||||
|
||||
game.addPlayer(1, player1)
|
||||
game.addPlayer(2, player2)
|
||||
|
||||
game.Deal()
|
||||
return game
|
||||
}
|
||||
|
||||
func (g *Game) Initialize() {
|
||||
g.CommandQueue = make(chan GameCommand, 10)
|
||||
g.Deck = joker.NewDeck(joker.StandardCards, 0)
|
||||
|
||||
g.Dealer = rand.Intn(2) + 1
|
||||
if g.Dealer == 1 {
|
||||
g.Turn = 2
|
||||
} else {
|
||||
g.Turn = 1
|
||||
}
|
||||
|
||||
g.Reset()
|
||||
}
|
||||
|
||||
func (g *Game) Reset() {
|
||||
g.Deck.Shuffle()
|
||||
g.Hand1 = Cards{}
|
||||
g.Hand2 = Cards{}
|
||||
g.Crib = PlayerCards{}
|
||||
g.Starter = Card{}
|
||||
g.ThrowPile = PlayerCards{}
|
||||
g.DiscardPile = PlayerCards{}
|
||||
|
||||
// TODO SHUFFLE_MIN + rand.Intn(SHUFFLE_MAX-SHUFFLE_MIN)
|
||||
g.Deck.Shuffle()
|
||||
|
||||
g.PegPhase = PegPhaseNormal
|
||||
g.Phase = PhaseSetup
|
||||
}
|
||||
|
||||
func (g *Game) addPlayer(player int, client *Client) {
|
||||
client.game = g
|
||||
client.gameplayer = player
|
||||
client.gameready = true
|
||||
|
||||
if player == 1 {
|
||||
g.Player1 = client
|
||||
} else {
|
||||
g.Player2 = client
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) Deal() {
|
||||
if g.Phase != PhaseSetup {
|
||||
return
|
||||
}
|
||||
|
||||
var dealtcards joker.Cards
|
||||
var ok bool
|
||||
g.Phase = PhasePick
|
||||
g.ResetGo()
|
||||
g.ResetReady()
|
||||
|
||||
for i := 0; i < 12; i++ {
|
||||
dealtcards, ok = g.Deck.Draw(1)
|
||||
if !ok {
|
||||
log.Fatalf("failed to deal: not enough cards")
|
||||
}
|
||||
|
||||
if i%2 == 0 {
|
||||
g.Hand1 = append(g.Hand1, dealtcards[0])
|
||||
} else {
|
||||
g.Hand2 = append(g.Hand2, dealtcards[0])
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(g.Hand1)
|
||||
sort.Sort(g.Hand2)
|
||||
}
|
||||
|
||||
func (g *Game) Cut(cut int) bool {
|
||||
if g.Phase != PhasePick || g.Crib.Len() < 4 || g.Starter.Value() > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
g.logDebug("Cutting @", cut)
|
||||
g.Starter = g.Deck.Cards[cut]
|
||||
g.Phase = PhasePeg
|
||||
|
||||
for msgplayer := 1; msgplayer <= 2; msgplayer++ {
|
||||
if g.getClient(msgplayer).ConnType == ClientTelnet {
|
||||
g.getClient(msgplayer).write("Starter: " + g.Starter.String())
|
||||
} else {
|
||||
g.update(msgplayer)
|
||||
}
|
||||
}
|
||||
|
||||
if g.Starter.Identifier() == "j" {
|
||||
g.award(g.Dealer, 2, cribbage.Peg, "Nibs")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *Game) exportJSON(player int) (string, error) {
|
||||
gamestate := make(map[string]string)
|
||||
|
||||
gamereflected := reflect.ValueOf(g).Elem()
|
||||
for i := 0; i < gamereflected.NumField(); i++ {
|
||||
fieldname := gamereflected.Type().Field(i).Name
|
||||
fieldtag := gamereflected.Type().Field(i).Tag.Get("json")
|
||||
if fieldtag == "" || fieldtag == "-" {
|
||||
continue
|
||||
} else if g.Phase != PhaseScore && (fieldname == "Hand1" && player != 1) || (fieldname == "Hand2" && player != 2) {
|
||||
hand := gamereflected.Field(i).Interface().(joker.Cards)
|
||||
handprinted := ""
|
||||
for j := 0; j < hand.Len(); j++ {
|
||||
if j > 0 {
|
||||
handprinted += ","
|
||||
}
|
||||
handprinted += "\"??\""
|
||||
}
|
||||
|
||||
gamestate[fieldtag] = fmt.Sprintf("[%s]", handprinted)
|
||||
} else if g.Phase != PhaseScore && fieldname == "Crib" {
|
||||
hand := gamereflected.Field(i).Interface().(PlayerCards)
|
||||
handprinted := ""
|
||||
for j := 0; j < hand.Len(); j++ {
|
||||
if j > 0 {
|
||||
handprinted += ","
|
||||
}
|
||||
handprinted += "\"??\""
|
||||
}
|
||||
|
||||
gamestate[fieldtag] = fmt.Sprintf("[%s]", handprinted)
|
||||
} else {
|
||||
jsonvalue, err := json.Marshal(gamereflected.Field(i).Interface())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
gamestate[fieldtag] = string(jsonvalue)
|
||||
}
|
||||
}
|
||||
|
||||
if player > 0 {
|
||||
gamestate["player"] = strconv.Itoa(player)
|
||||
|
||||
jsonvalue, err := json.Marshal(g.getClient(player).gamewaiting)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
gamestate["waiting"] = string(jsonvalue)
|
||||
}
|
||||
|
||||
throwPileSum := cribbage.Sum(g.ThrowPile.Cards())
|
||||
gamestate["throwpilesum"] = strconv.Itoa(throwPileSum)
|
||||
|
||||
throwpilescore, _ := cribbage.Score(cribbage.Peg, g.ThrowPile.Cards(), g.Starter)
|
||||
if g.Phase == PhasePeg && g.PegPhase == PegPhaseFinal && g.PeggingFinished() && throwPileSum != 31 {
|
||||
throwpilescore++
|
||||
}
|
||||
|
||||
gamestate["throwpilescore"] = strconv.Itoa(throwpilescore)
|
||||
|
||||
gamestatelines := []string{}
|
||||
for statefield, statevalue := range gamestate {
|
||||
gamestatelines = append(gamestatelines, fmt.Sprintf("\"%s\": %s", statefield, statevalue))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("{%s}", strings.Join(gamestatelines, ",")), nil
|
||||
}
|
||||
|
||||
func (g *Game) Peg(player int) int {
|
||||
if g.Phase != PhasePeg || g.Turn != player {
|
||||
log.Println("Error, not players turn or other invalid state")
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
pegscore, _ := cribbage.Score(cribbage.Peg, g.ThrowPile.Cards(), g.Starter)
|
||||
if pegscore > 0 {
|
||||
g.award(g.Turn, pegscore, cribbage.Peg, "")
|
||||
}
|
||||
|
||||
return pegscore
|
||||
}
|
||||
|
||||
func (g *Game) award(player int, score int, scoretype cribbage.ScoringType, reason string) int {
|
||||
if g.Phase != PhaseEnd {
|
||||
points := "point"
|
||||
if score != 1 {
|
||||
points = "points"
|
||||
}
|
||||
|
||||
st := "scored"
|
||||
if scoretype == cribbage.Peg {
|
||||
st = "pegged"
|
||||
}
|
||||
|
||||
if reason != "" {
|
||||
reason = " - " + reason
|
||||
}
|
||||
|
||||
for msgplayer := 1; msgplayer <= 2; msgplayer++ {
|
||||
g.getClient(msgplayer).write(fmt.Sprintf("%s %s %d %s%s", g.getName(msgplayer, player), st, score, points, reason))
|
||||
}
|
||||
|
||||
if player == 1 {
|
||||
g.Score1 += score
|
||||
} else {
|
||||
g.Score2 += score
|
||||
}
|
||||
|
||||
if g.getScore(player) >= WinningScore {
|
||||
g.end(player, "Won!")
|
||||
}
|
||||
}
|
||||
|
||||
return g.getScore(player)
|
||||
}
|
||||
|
||||
func (g *Game) scoreHands() (int, int, int) {
|
||||
if g.Phase != PhasePeg || g.Hand1.Len() > 0 || g.Hand2.Len() > 0 {
|
||||
log.Println("Error, not pegging phase or player still has cards")
|
||||
|
||||
return -1, -1, -1
|
||||
}
|
||||
|
||||
// TODO: Store player in card data or separate slice
|
||||
g.Phase = PhaseScore
|
||||
for _, card := range g.ThrowPile {
|
||||
if card.Player == 1 {
|
||||
g.Hand1 = append(g.Hand1, card.Card)
|
||||
} else {
|
||||
g.Hand2 = append(g.Hand2, card.Card)
|
||||
}
|
||||
}
|
||||
for _, card := range g.DiscardPile {
|
||||
if card.Player == 1 {
|
||||
g.Hand1 = append(g.Hand1, card.Card)
|
||||
} else {
|
||||
g.Hand2 = append(g.Hand2, card.Card)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(g.Hand1)
|
||||
sort.Sort(g.Hand2)
|
||||
|
||||
opponentHandScore, _ := cribbage.Score(cribbage.ShowHand, *g.getHand(g.getOpponent(g.Dealer)), g.Starter)
|
||||
dealerHandScore, _ := cribbage.Score(cribbage.ShowHand, *g.getHand(g.Dealer), g.Starter)
|
||||
dealerCribScore, _ := cribbage.Score(cribbage.Peg, g.Crib.Cards(), g.Starter)
|
||||
|
||||
// Awarding order is important
|
||||
opponentscore := g.award(g.getOpponent(g.Dealer), opponentHandScore, cribbage.ShowHand, "Hand")
|
||||
dealerscore := g.award(g.Dealer, dealerHandScore, cribbage.ShowHand, "Hand")
|
||||
dealercribscore := g.award(g.Dealer, dealerCribScore, cribbage.ShowHand, "Crib")
|
||||
|
||||
var yourscore string
|
||||
var theirscore string
|
||||
dealerscoreprinted := fmt.Sprintf("%d [%d-%d]", dealerHandScore+dealerCribScore, dealerHandScore, dealerCribScore)
|
||||
opponentscoreprinted := fmt.Sprintf("%d", opponentHandScore)
|
||||
for msgplayer := 1; msgplayer <= 2; msgplayer++ {
|
||||
if g.Dealer == msgplayer {
|
||||
yourscore = dealerscoreprinted
|
||||
theirscore = opponentscoreprinted
|
||||
} else {
|
||||
yourscore = opponentscoreprinted
|
||||
theirscore = dealerscoreprinted
|
||||
}
|
||||
|
||||
g.getClient(msgplayer).gameready = false
|
||||
g.getClient(msgplayer).gamewaiting = "You scored " + yourscore + " - Your opponent scored " + theirscore
|
||||
}
|
||||
|
||||
return opponentscore, dealerscore, dealercribscore
|
||||
}
|
||||
|
||||
func (g *Game) end(player int, reason string) {
|
||||
g.CommandQueue <- GameCommand{Player: player, Command: CommandEnd, Value: reason}
|
||||
}
|
||||
|
||||
func (g *Game) Throw(player int, cardidentifier string) bool {
|
||||
if g.Phase == PhaseScore {
|
||||
return false
|
||||
} else if g.Phase == PhasePick {
|
||||
// TODO Store players of thrown cards or check number of cards still left in thrower hand
|
||||
if g.Crib.PlayerCards(player) == 2 {
|
||||
log.Println("Error, already thrown")
|
||||
|
||||
return false
|
||||
}
|
||||
} else if g.Phase == PhasePeg && (g.Turn != player || g.PegPhase == PegPhaseFinal) {
|
||||
log.Println("Error, not players turn")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
card, ok := Parse(cleanIdentifier(cardidentifier))
|
||||
if !ok {
|
||||
log.Println("Attempted to throw invalid card:", player, cardidentifier)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
playerHand := g.getHand(player)
|
||||
opponentHand := g.getHand(g.getOpponent(player))
|
||||
|
||||
if !playerHand.Contains(card) {
|
||||
log.Println("Attempted to throw card not in hand:", player, cardidentifier, playerHand)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if g.Phase == PhasePeg {
|
||||
if cribbage.Sum(g.ThrowPile.Cards())+cribbage.Value(card) > 31 {
|
||||
g.getClient(player).write("Error: Illegal throw, would exceed 31")
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
*playerHand = (*playerHand).Remove(card)
|
||||
|
||||
if g.Phase == PhasePick {
|
||||
g.Crib = append(g.Crib, PlayerCard{card, player})
|
||||
sort.Sort(g.Crib)
|
||||
/*if len(g.Crib.Cards) == 4 && player == g.Dealer {
|
||||
g.updateOpponent(player)
|
||||
}*/
|
||||
// TODO: Temporary before cut UI
|
||||
if len(g.Crib) == 4 {
|
||||
g.Cut(rand.Intn(32) + 4)
|
||||
}
|
||||
} else if g.Phase == PhasePeg {
|
||||
g.ThrowPile = append(g.ThrowPile, PlayerCard{card, player})
|
||||
g.Peg(player)
|
||||
|
||||
if cribbage.Sum(g.ThrowPile.Cards()) == 31 {
|
||||
log.Println("=31 move to solo")
|
||||
g.PegPhase = PegPhaseSolo
|
||||
g.pegTurn(player)
|
||||
} else if cribbage.Sum(g.ThrowPile.Cards())+cribbage.Value(opponentHand.Low()) > 31 {
|
||||
if opponentHand.Len() > 0 && !g.getClient(g.getOpponent(player)).gamego {
|
||||
log.Println("Setting Go - Player", g.getOpponent(player))
|
||||
g.getClient(g.getOpponent(player)).gamego = true
|
||||
g.getClient(g.getOpponent(player)).gamewaiting = "Go"
|
||||
g.getClient(g.getOpponent(player)).gameready = false
|
||||
|
||||
g.NextTurn()
|
||||
} else if g.PeggingFinished() {
|
||||
log.Println(">31 pegging finished move to solo")
|
||||
g.PegPhase = PegPhaseSolo
|
||||
g.pegTurn(player)
|
||||
}
|
||||
} else {
|
||||
g.NextTurn()
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *Game) PeggingFinished() bool {
|
||||
return cribbage.Sum(g.ThrowPile.Cards())+cribbage.Value(g.getHand(1).Low()) > 31 && cribbage.Sum(g.ThrowPile.Cards())+cribbage.Value(g.getHand(2).Low()) > 31
|
||||
}
|
||||
|
||||
func (g *Game) ResetGo() {
|
||||
if g.Phase > 0 {
|
||||
g.logDebug("Reset go")
|
||||
g.Player1.gamego = false
|
||||
g.Player2.gamego = false
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) ResetReady() {
|
||||
if g.Phase > 0 {
|
||||
g.Player1.gameready = true
|
||||
g.Player1.gamewaiting = ""
|
||||
g.Player2.gameready = true
|
||||
g.Player2.gamewaiting = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) Ready(player int) {
|
||||
g.getClient(player).gameready = true
|
||||
g.getClient(player).gamewaiting = "Waiting on opponent..."
|
||||
}
|
||||
|
||||
func (g *Game) AllReady() bool {
|
||||
return g.Player1.gameready && g.Player2.gameready
|
||||
}
|
||||
|
||||
func (g *Game) NextTurn() {
|
||||
g.logDebug("Turn...")
|
||||
if g.Turn == 1 {
|
||||
g.Turn = 2
|
||||
} else {
|
||||
g.Turn = 1
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) NextHand() {
|
||||
g.Turn = g.Dealer
|
||||
g.Dealer = g.getOpponent(g.Dealer)
|
||||
g.Reset()
|
||||
g.Deal()
|
||||
}
|
||||
|
||||
func (g *Game) getClient(player int) *Client {
|
||||
if player == 1 {
|
||||
return g.Player1
|
||||
} else {
|
||||
return g.Player2
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) getOpponent(player int) int {
|
||||
if player == 1 {
|
||||
return 2
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) getName(player int, clientplayer int) string {
|
||||
if player == clientplayer {
|
||||
return "You"
|
||||
} else {
|
||||
return "Your opponent"
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) getScore(player int) int {
|
||||
if player == 1 {
|
||||
return g.Score1
|
||||
} else {
|
||||
return g.Score2
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) getHand(player int) *Cards {
|
||||
if player == 1 {
|
||||
return &g.Hand1
|
||||
} else {
|
||||
return &g.Hand2
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) update(player int) {
|
||||
g.sendGameState(player)
|
||||
}
|
||||
|
||||
func (g *Game) updateOpponent(player int) {
|
||||
if player == 1 {
|
||||
g.update(2)
|
||||
} else {
|
||||
g.update(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) updateAll() {
|
||||
g.update(1)
|
||||
g.update(2)
|
||||
}
|
||||
|
||||
func (g *Game) printScoring() {
|
||||
for player := 1; player <= 2; player++ {
|
||||
gameprinted := []string{}
|
||||
|
||||
opponentscore, _ := cribbage.Score(cribbage.ShowHand, *g.getHand(g.getOpponent(g.Dealer)), g.Starter)
|
||||
dealerscore, _ := cribbage.Score(cribbage.ShowHand, *g.getHand(g.Dealer), g.Starter)
|
||||
dealercribscore, _ := cribbage.Score(cribbage.ShowCrib, g.Crib.Cards(), g.Starter)
|
||||
|
||||
dealerscoreprinted := fmt.Sprintf("%d [%d-%d]", dealerscore+dealercribscore, dealerscore, dealercribscore)
|
||||
opponentscoreprinted := fmt.Sprintf("%d", opponentscore)
|
||||
|
||||
var yourscore string
|
||||
var theirscore string
|
||||
if g.Dealer == player {
|
||||
yourscore = dealerscoreprinted
|
||||
theirscore = opponentscoreprinted
|
||||
} else {
|
||||
yourscore = opponentscoreprinted
|
||||
theirscore = dealerscoreprinted
|
||||
}
|
||||
gameprinted = append(gameprinted, "Your hand(s) scored "+yourscore+" - Your opponent's hand(s) scored "+theirscore)
|
||||
|
||||
gameprinted = append(gameprinted, "Starter "+g.Starter.String())
|
||||
gameprinted = append(gameprinted, fmt.Sprintf("Opponent (%2d) %s", opponentscore, g.getHand(g.getOpponent(g.Dealer))))
|
||||
gameprinted = append(gameprinted, fmt.Sprintf("Dealer (%2d) %s", dealerscore, g.getHand(g.Dealer)))
|
||||
gameprinted = append(gameprinted, fmt.Sprintf("Dealer c (%2d) %s", dealercribscore, g.Crib))
|
||||
|
||||
g.getClient(player).write(strings.Join(gameprinted, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) printGame(player int) string {
|
||||
lightprint := false
|
||||
gameprinted := []string{}
|
||||
|
||||
if g.Phase == PhasePeg {
|
||||
gameprinted = append(gameprinted, "Pegging: "+g.ThrowPile.String()+" ("+strconv.Itoa(cribbage.Sum(g.ThrowPile.Cards()))+")")
|
||||
|
||||
if g.Turn == player {
|
||||
gameprinted = append(gameprinted, fmt.Sprintf("Please throw a card. (Enter 1-%d)", g.getHand(player).Len()))
|
||||
} else if g.getHand(player).Len() > 0 && cribbage.Sum(g.ThrowPile.Cards())+cribbage.Value(g.getHand(player).Low()) > 31 {
|
||||
lightprint = true
|
||||
gameprinted = append(gameprinted, "Go - Waiting on opponent...")
|
||||
} else {
|
||||
lightprint = true
|
||||
gameprinted = append(gameprinted, "Waiting on opponent...")
|
||||
}
|
||||
}
|
||||
|
||||
gameprintedstr := ""
|
||||
if g.Phase == PhasePick {
|
||||
cardsthrown := 0
|
||||
for _, card := range g.Crib {
|
||||
if card.Player == player {
|
||||
cardsthrown++
|
||||
}
|
||||
}
|
||||
|
||||
if cardsthrown < 2 {
|
||||
if cardsthrown == 0 {
|
||||
gameprintedstr += "Please throw two cards for "
|
||||
} else {
|
||||
gameprintedstr += "Please throw one additional card for "
|
||||
}
|
||||
if g.Dealer == player {
|
||||
gameprintedstr += "your"
|
||||
} else {
|
||||
gameprintedstr += "your opponent's"
|
||||
}
|
||||
gameprintedstr += fmt.Sprintf(" crib. (Enter 1-%d, throw multiple cards by separating card#'s with a space)", g.getHand(player).Len())
|
||||
} else if len(g.Crib) < 4 {
|
||||
lightprint = true
|
||||
gameprintedstr += "Waiting on opponent..."
|
||||
} else if g.Starter.Value() == 0 {
|
||||
lightprint = true
|
||||
if g.Dealer != player {
|
||||
gameprintedstr += "Please cut the deck. (Enter 4-36 or c/cut for a random cut)"
|
||||
} else {
|
||||
gameprintedstr += "Waiting on opponent..."
|
||||
}
|
||||
}
|
||||
gameprinted = append(gameprinted, gameprintedstr)
|
||||
gameprintedstr = ""
|
||||
}
|
||||
|
||||
if !lightprint {
|
||||
if len(gameprinted) > 0 {
|
||||
gameprinted = append(gameprinted, "")
|
||||
}
|
||||
gameprinted = append(gameprinted, fmt.Sprintf("Score: (%s) %d / %d (%s)", g.getName(1, player), g.getScore(1), g.getScore(2), g.getName(2, player)))
|
||||
gameprinted = append(gameprinted, "Your cards: "+g.getHand(player).String())
|
||||
}
|
||||
|
||||
return strings.Join(gameprinted, "\n")
|
||||
}
|
||||
|
||||
func (g *Game) printAll() {
|
||||
log.Println("Printing game state...")
|
||||
|
||||
var phaseprinted string
|
||||
switch g.Phase {
|
||||
case PhaseSetup:
|
||||
phaseprinted = "Setup"
|
||||
break
|
||||
case PhasePick:
|
||||
phaseprinted = "Pick"
|
||||
break
|
||||
case PhasePeg:
|
||||
phaseprinted = "Peg"
|
||||
|
||||
switch g.Phase {
|
||||
case PegPhaseNormal:
|
||||
phaseprinted += " Normal"
|
||||
break
|
||||
case PegPhaseSolo:
|
||||
phaseprinted += " Solo"
|
||||
break
|
||||
case PegPhaseFinal:
|
||||
phaseprinted += " Final"
|
||||
break
|
||||
}
|
||||
|
||||
break
|
||||
case PhaseScore:
|
||||
phaseprinted = "Score"
|
||||
break
|
||||
case PhaseEnd:
|
||||
phaseprinted = "End"
|
||||
}
|
||||
log.Printf("[%s] Dealer: %d - Turn: %d", phaseprinted, g.Dealer, g.Turn)
|
||||
if g.Phase > 0 {
|
||||
log.Printf("Player 1 ready: %t - Player 2 ready: %t", g.Player1.gameready, g.Player2.gameready)
|
||||
}
|
||||
|
||||
var starter Card
|
||||
if g.Starter.Value() > 0 {
|
||||
starter = g.Starter
|
||||
}
|
||||
|
||||
hand1Score, hand1ScoreResults := cribbage.Score(cribbage.ShowHand, g.Hand1, starter)
|
||||
log.Println("Hand 1 [", hand1Score, "]", g.Hand1.String(), hand1ScoreResults)
|
||||
|
||||
cribScore, cribScoreResults := cribbage.Score(cribbage.ShowCrib, g.Crib.Cards(), starter)
|
||||
log.Println("Crib [", cribScore, "]", g.Crib.Cards().String(), cribScoreResults)
|
||||
|
||||
hand2Score, hand2ScoreResults := cribbage.Score(cribbage.ShowHand, g.Hand2, starter)
|
||||
log.Println("Hand 2 [", hand2Score, "]", g.Hand2.String(), hand2ScoreResults)
|
||||
|
||||
if g.Starter.Value() > 0 {
|
||||
log.Println("Starter:", g.Starter.String())
|
||||
}
|
||||
|
||||
if g.ThrowPile.Len() > 0 {
|
||||
pegScore, pegResults := cribbage.Score(cribbage.Peg, g.ThrowPile.Cards(), starter)
|
||||
log.Println("Pegging: [", pegScore, "]", g.ThrowPile.Cards().String(), pegResults)
|
||||
}
|
||||
|
||||
if g.DiscardPile.Len() > 0 {
|
||||
log.Println("Discard:", g.DiscardPile.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) sendGameState(player int) {
|
||||
client := g.getClient(player)
|
||||
if client.ConnType == ClientWebsocket {
|
||||
gamestate, err := g.exportJSON(player)
|
||||
if err != nil {
|
||||
log.Fatal("failed to marshal game state:", err)
|
||||
}
|
||||
client.write(string(gamestate))
|
||||
} else {
|
||||
client.write(g.printGame(player))
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) pegTurn(player int) {
|
||||
if g.Phase != PhasePeg {
|
||||
return
|
||||
}
|
||||
|
||||
g.logDebug("PEG CONTINUE")
|
||||
if cribbage.Sum(g.ThrowPile.Cards())+cribbage.Value(g.getHand(player).Low()) > 31 && g.PegPhase == PegPhaseNormal {
|
||||
g.logDebug("PEG NEXT TURN")
|
||||
|
||||
if g.PeggingFinished() {
|
||||
g.ResetGo()
|
||||
|
||||
g.PegPhase = PegPhaseSolo
|
||||
g.getClient(g.getOpponent(player)).gameready = false
|
||||
g.getClient(g.getOpponent(player)).gamewaiting = "Go - Pegging finished"
|
||||
}
|
||||
|
||||
g.NextTurn()
|
||||
} else if g.PeggingFinished() {
|
||||
if g.PegPhase == PegPhaseSolo {
|
||||
g.logDebug("ENTERING FINAL PEG")
|
||||
g.PegPhase = PegPhaseFinal
|
||||
|
||||
finalpeg, _ := cribbage.Score(cribbage.Peg, g.ThrowPile.Cards(), g.Starter)
|
||||
if cribbage.Sum(g.ThrowPile.Cards()) != 31 {
|
||||
finalpeg++
|
||||
g.award(g.Turn, 1, cribbage.Peg, "Last card")
|
||||
}
|
||||
|
||||
g.getClient(g.Turn).gameready = false
|
||||
g.getClient(g.Turn).gamewaiting = "You pegged " + strconv.Itoa(finalpeg) + " point"
|
||||
if finalpeg != 1 {
|
||||
g.getClient(g.Turn).gamewaiting += "s"
|
||||
}
|
||||
g.getClient(g.getOpponent(g.Turn)).gameready = false
|
||||
g.getClient(g.getOpponent(g.Turn)).gamewaiting = "Your opponent pegged " + strconv.Itoa(finalpeg) + " point"
|
||||
if finalpeg != 1 {
|
||||
g.getClient(g.getOpponent(g.Turn)).gamewaiting += "s"
|
||||
}
|
||||
g.NextTurn()
|
||||
} else if g.PegPhase == PegPhaseFinal {
|
||||
g.logDebug("FINAL PEG")
|
||||
g.PegPhase = PegPhaseNormal
|
||||
|
||||
if g.Hand1.Len() == 0 && g.Hand2.Len() == 0 {
|
||||
if cribbage.Sum(g.ThrowPile.Cards()) != 31 {
|
||||
g.award(g.Turn, 1, cribbage.Peg, "Last card")
|
||||
}
|
||||
|
||||
g.scoreHands()
|
||||
g.printScoring()
|
||||
|
||||
g.NextTurn()
|
||||
} else {
|
||||
g.DiscardPile = append(g.DiscardPile, g.ThrowPile...)
|
||||
g.ThrowPile = PlayerCards{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) processGameCommand(command GameCommand) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
if command.Command == CommandEnd {
|
||||
g.Phase = PhaseEnd
|
||||
g.updateAll()
|
||||
return
|
||||
}
|
||||
|
||||
command_args := strings.Fields(command.Value)
|
||||
|
||||
if command.Command == CommandRaw {
|
||||
if g.Phase == PhasePick && g.Crib.Len() == 4 && g.Starter.Value() == 0 {
|
||||
command.Command = CommandCut
|
||||
} else {
|
||||
command.Command = CommandThrow
|
||||
}
|
||||
}
|
||||
|
||||
if g.Phase == PhasePick && command.Command == CommandCut {
|
||||
if len(command_args) > 0 {
|
||||
cut, err := strconv.Atoi(command_args[0])
|
||||
if err == nil && cut >= 4 && cut <= 36 {
|
||||
g.Cut(cut - 1)
|
||||
}
|
||||
}
|
||||
|
||||
g.Cut(rand.Intn(32) + 4)
|
||||
} else if command.Command == CommandContinue {
|
||||
if g.getClient(command.Player).gameready {
|
||||
return
|
||||
}
|
||||
|
||||
g.Ready(command.Player)
|
||||
if g.AllReady() {
|
||||
g.Player1.gamewaiting = ""
|
||||
g.Player2.gamewaiting = ""
|
||||
|
||||
g.logDebug("ALL READY")
|
||||
if g.Phase == PhasePeg {
|
||||
g.pegTurn(command.Player)
|
||||
} else if g.Phase == PhaseScore {
|
||||
g.NextHand()
|
||||
g.update(g.getOpponent(command.Player))
|
||||
}
|
||||
}
|
||||
} else if command.Command == CommandThrow {
|
||||
if g.Phase == PhasePick || g.Phase == PhasePeg {
|
||||
cards := []string{}
|
||||
|
||||
lastindex := 0
|
||||
for i, cardarg := range command_args {
|
||||
cardindex, err := strconv.Atoi(cardarg)
|
||||
if err == nil {
|
||||
if cardindex == lastindex {
|
||||
break
|
||||
}
|
||||
|
||||
if cardindex >= 1 && g.getHand(command.Player).Len() >= cardindex {
|
||||
cards = append(cards, (*g.getHand(command.Player))[(cardindex-1)].Identifier())
|
||||
lastindex = cardindex
|
||||
}
|
||||
} else {
|
||||
cards = append(cards, cardarg)
|
||||
}
|
||||
|
||||
if i > 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, card := range cards {
|
||||
g.Throw(command.Player, card)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g.updateAll()
|
||||
}
|
||||
|
||||
func (g *Game) playGame() {
|
||||
log.Println("Starting new game")
|
||||
g.updateAll()
|
||||
|
||||
for command := range g.CommandQueue {
|
||||
g.logDebug("Received GameCommand", command.ToStr())
|
||||
g.processGameCommand(command)
|
||||
|
||||
if g.Phase == PhaseEnd {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) logDebug(args ...interface{}) {
|
||||
log.Println("phase:", g.Phase,
|
||||
"turn:", g.Turn,
|
||||
"dealer:", g.Dealer, args)
|
||||
|
||||
}
|
||||
|
||||
func cleanIdentifier(cardidentifier string) string {
|
||||
return strings.ToLower(strings.TrimLeft(strings.TrimRight(cardidentifier, " \r\n"+string(0)), " \r\n"+string(0)))
|
||||
}
|
35
gamecommand.go
Normal file
35
gamecommand.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const CommandEnd = -1
|
||||
const CommandRaw = 0
|
||||
const CommandContinue = 1
|
||||
const CommandThrow = 2
|
||||
const CommandCut = 3
|
||||
|
||||
type GameCommand struct {
|
||||
Player int
|
||||
Command int
|
||||
Value string
|
||||
}
|
||||
|
||||
func (gc GameCommand) ToStr() string {
|
||||
var commandprinted string
|
||||
switch gc.Command {
|
||||
case CommandEnd:
|
||||
commandprinted = "End"
|
||||
break
|
||||
case CommandRaw:
|
||||
commandprinted = "Raw"
|
||||
case CommandContinue:
|
||||
commandprinted = "Continue"
|
||||
break
|
||||
case CommandCut:
|
||||
commandprinted = "Cut"
|
||||
break
|
||||
case CommandThrow:
|
||||
commandprinted = "Throw"
|
||||
}
|
||||
return fmt.Sprintf("Player %d - %s - %s", gc.Player, commandprinted, gc.Value)
|
||||
}
|
9
go.mod
Normal file
9
go.mod
Normal file
|
@ -0,0 +1,9 @@
|
|||
module gitlab.com/tslocum/jack
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
gitlab.com/tslocum/joker v0.1.3-0.20200529165915-df7bd71a3bcd
|
||||
gitlab.com/tslocum/joker-cribbage v0.1.2-0.20200206160759-4543b916d838
|
||||
)
|
8
go.sum
Normal file
8
go.sum
Normal file
|
@ -0,0 +1,8 @@
|
|||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
gitlab.com/tslocum/joker v0.1.2 h1:7ujvgkGNUJbrvpXvNHSvKWFDYIKTCWbvFcWL1IbRVWA=
|
||||
gitlab.com/tslocum/joker v0.1.2/go.mod h1:bxTQ0FFmBP465r9z76zcm97S4Ld9eCLa3q20TyVM82A=
|
||||
gitlab.com/tslocum/joker v0.1.3-0.20200529165915-df7bd71a3bcd h1:nDmVh1g7yCbffyAozEPQsCRHc1MQ30P+u5FQQlZwa8A=
|
||||
gitlab.com/tslocum/joker v0.1.3-0.20200529165915-df7bd71a3bcd/go.mod h1:bxTQ0FFmBP465r9z76zcm97S4Ld9eCLa3q20TyVM82A=
|
||||
gitlab.com/tslocum/joker-cribbage v0.1.2-0.20200206160759-4543b916d838 h1:Z3Sv+ArlCL2NezHmv6U6HA2GOY40Ed8+eCu4WrIZvdk=
|
||||
gitlab.com/tslocum/joker-cribbage v0.1.2-0.20200206160759-4543b916d838/go.mod h1:/fD4xxguXPg+N9s5+Q6t6tib1xfvh7zZ6AQ6TZXViDA=
|
29
main.go
Normal file
29
main.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
go func() {
|
||||
log.Fatal(http.ListenAndServe(":8880", nil))
|
||||
}()
|
||||
|
||||
log.Println("CribServer initialized")
|
||||
|
||||
cs := CribServer{}
|
||||
cs.clientqueuealert = make(chan bool)
|
||||
|
||||
go cs.matchPlayers()
|
||||
go cs.listenWebSocket()
|
||||
|
||||
cs.listenTelnet()
|
||||
|
||||
// TODO
|
||||
select {}
|
||||
}
|
98
server.go
Normal file
98
server.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var websocketUpgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type CribServer struct {
|
||||
clients []*Client
|
||||
clientqueue []*Client
|
||||
clientqueuealert chan bool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (cs *CribServer) handleReadWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("New WebSocket")
|
||||
|
||||
c, err := websocketUpgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("WebSocket upgrade error:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
client := &Client{ConnType: ClientWebsocket, ConnWebsocket: c}
|
||||
client.initialize()
|
||||
cs.addClient(client)
|
||||
}
|
||||
|
||||
func (cs *CribServer) listenWebSocket() {
|
||||
http.HandleFunc("/crib", cs.handleReadWebSocket)
|
||||
http.ListenAndServe(":8884", nil)
|
||||
}
|
||||
|
||||
func (cs *CribServer) addClient(client *Client) {
|
||||
cs.Lock()
|
||||
cs.clients = append(cs.clients, client)
|
||||
cs.Unlock()
|
||||
|
||||
go client.processRead()
|
||||
go client.handleWrite()
|
||||
cs.queueClient(client)
|
||||
client.handleRead()
|
||||
}
|
||||
|
||||
func (cs *CribServer) listenTelnet() {
|
||||
ln, err := net.Listen("tcp", ":8888")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
client := &Client{ConnType: ClientTelnet, ConnTelnet: conn}
|
||||
client.initialize()
|
||||
go cs.addClient(client)
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *CribServer) queueClient(client *Client) {
|
||||
cs.Lock()
|
||||
cs.clientqueue = append(cs.clientqueue, client)
|
||||
cs.Unlock()
|
||||
cs.clientqueuealert <- true
|
||||
}
|
||||
|
||||
func (cs *CribServer) matchPlayers() {
|
||||
for range cs.clientqueuealert {
|
||||
cs.Lock()
|
||||
if len(cs.clientqueue) > 1 {
|
||||
go cs.startGame(cs.clientqueue[0], cs.clientqueue[1])
|
||||
cs.clientqueue = cs.clientqueue[2:]
|
||||
}
|
||||
cs.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *CribServer) startGame(player1, player2 *Client) {
|
||||
game := NewGame(player1, player2)
|
||||
game.playGame()
|
||||
}
|
Loading…
Reference in a new issue