Initial commit
This commit is contained in:
BIN
uno/server/__debug_bin
Normal file
BIN
uno/server/__debug_bin
Normal file
Binary file not shown.
8
uno/server/go.mod
Normal file
8
uno/server/go.mod
Normal file
@@ -0,0 +1,8 @@
|
||||
module kingofdog.de/projects/uno
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
)
|
4
uno/server/go.sum
Normal file
4
uno/server/go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
87
uno/server/helpers.go
Normal file
87
uno/server/helpers.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func GetDefaultDeck() []Card {
|
||||
cards := make([]Card, 0)
|
||||
for _, color := range []string{"GREEN", "BLUE", "RED", "YELLOW"} {
|
||||
for i := 0; i < 10; i++ {
|
||||
cards = append(cards, Card{
|
||||
color, strconv.Itoa(i),
|
||||
})
|
||||
}
|
||||
cards = append(cards, Card{
|
||||
color, "RETURN",
|
||||
})
|
||||
cards = append(cards, Card{
|
||||
color, "BLOCK",
|
||||
})
|
||||
cards = append(cards, Card{
|
||||
color, "DRAW2",
|
||||
})
|
||||
}
|
||||
cards = append(cards, Card{
|
||||
"BLACK", "CHOOSE",
|
||||
})
|
||||
cards = append(cards, Card{
|
||||
"BLACK", "DRAW4",
|
||||
})
|
||||
return cards
|
||||
}
|
||||
|
||||
func GetGame(id string) *Game {
|
||||
return games[id]
|
||||
}
|
||||
|
||||
func GetRandomCard(cards []Card) *Card {
|
||||
index := rand.Intn(len(cards))
|
||||
return &cards[index]
|
||||
}
|
||||
|
||||
func FindCard(cards []Card, color string, value string) (*Card, bool) {
|
||||
for _, card := range cards {
|
||||
if card.Color == color && card.Value == value {
|
||||
return &card, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func FindOnHand(player *Player, card *Card) int {
|
||||
for index, handCard := range player.Hand {
|
||||
if handCard.Color == card.Color && handCard.Value == card.Value {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func RemoveFromHand(player *Player, index int) []*Card {
|
||||
player.Hand[len(player.Hand)-1], player.Hand[index] = player.Hand[index], player.Hand[len(player.Hand)-1]
|
||||
return player.Hand[:len(player.Hand)-1]
|
||||
}
|
||||
|
||||
func InitPlayer(game *Game, player *Player) {
|
||||
player.Game = game
|
||||
for i := 0; i < 10; i++ {
|
||||
player.Hand = append(player.Hand, GetRandomCard(game.AvailableCards))
|
||||
}
|
||||
}
|
||||
|
||||
func DrawNextPlayer(game *Game, count int) {
|
||||
nextPlayer := game.GetNextPlayer()
|
||||
for i := 0; i < count; i++ {
|
||||
nextPlayer.Hand = append(nextPlayer.Hand, GetRandomCard(game.AvailableCards))
|
||||
}
|
||||
|
||||
game.send("card.drawn", map[string]interface{}{
|
||||
"playerID": nextPlayer.ID,
|
||||
"count": count,
|
||||
})
|
||||
nextPlayer.send("player.hand", map[string]interface{}{
|
||||
"cards": nextPlayer.Hand,
|
||||
})
|
||||
}
|
352
uno/server/main.go
Normal file
352
uno/server/main.go
Normal file
@@ -0,0 +1,352 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var games = make(map[string]*Game, 0)
|
||||
var players = make(map[string]*Player, 0)
|
||||
|
||||
var addr = flag.String("addr", ":8000", "http service address")
|
||||
|
||||
func (p *Player) send(messageType string, data map[string]interface{}) {
|
||||
dataObject := DataObject{
|
||||
Type: messageType,
|
||||
Data: data,
|
||||
}
|
||||
p.Connection.WriteJSON(dataObject)
|
||||
}
|
||||
|
||||
func (g *Game) send(messageType string, data map[string]interface{}) {
|
||||
for _, player := range g.Players {
|
||||
log.Println("sending message", player)
|
||||
player.send(messageType, data)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) GetNextPlayer() *Player {
|
||||
index := g.CurrentPlayerIndex
|
||||
if g.DirectionClockwise {
|
||||
index++
|
||||
} else {
|
||||
index--
|
||||
}
|
||||
|
||||
if index < 0 {
|
||||
index += len(g.Players)
|
||||
} else if index >= len(g.Players) {
|
||||
index -= len(g.Players)
|
||||
}
|
||||
|
||||
return g.Players[index]
|
||||
}
|
||||
|
||||
func (g *Game) nextTurn(block bool) {
|
||||
count := 1
|
||||
if block {
|
||||
count++
|
||||
}
|
||||
if g.DirectionClockwise {
|
||||
g.CurrentPlayerIndex += count
|
||||
} else {
|
||||
g.CurrentPlayerIndex -= count
|
||||
}
|
||||
|
||||
if g.CurrentPlayerIndex < 0 {
|
||||
g.CurrentPlayerIndex += len(g.Players)
|
||||
} else if g.CurrentPlayerIndex >= len(g.Players) {
|
||||
g.CurrentPlayerIndex -= len(g.Players)
|
||||
}
|
||||
|
||||
g.send("game.nextTurn", map[string]interface{}{
|
||||
"playerID": g.Players[g.CurrentPlayerIndex].ID,
|
||||
})
|
||||
}
|
||||
|
||||
func gameInitHandler(player *Player, data map[string]interface{}) {
|
||||
game := Game{
|
||||
//ID: uuid.New().String(),
|
||||
ID: "1234",
|
||||
MaxPlayers: 4,
|
||||
DirectionClockwise: true,
|
||||
AvailableCards: GetDefaultDeck(),
|
||||
}
|
||||
game.Players = append(game.Players, player)
|
||||
game.PlayingStack.Cards = append(game.PlayingStack.Cards, GetRandomCard(game.AvailableCards))
|
||||
game.CurrentPlayerIndex = 0
|
||||
games[game.ID] = &game
|
||||
InitPlayer(&game, player)
|
||||
|
||||
player.send("game.init.result", map[string]interface{}{
|
||||
"gameID": game.ID,
|
||||
"hand": player.Hand,
|
||||
"playerID": player.ID,
|
||||
"activePlayerID": game.Players[game.CurrentPlayerIndex].ID,
|
||||
"playingStack": game.PlayingStack.Cards,
|
||||
})
|
||||
}
|
||||
|
||||
func joinGameHandler(player *Player, data map[string]interface{}) {
|
||||
gameID, ok := data["gameID"]
|
||||
if !ok {
|
||||
log.Println("not found game id")
|
||||
return
|
||||
}
|
||||
|
||||
game, ok := games[gameID.(string)]
|
||||
if !ok {
|
||||
log.Println("game not found")
|
||||
return
|
||||
}
|
||||
|
||||
InitPlayer(game, player)
|
||||
game.send("game.joined", map[string]interface{}{
|
||||
"playerID": player.ID,
|
||||
"playerName": player.Name,
|
||||
"cardCount": len(player.Hand),
|
||||
})
|
||||
|
||||
players := make([]struct {
|
||||
PlayerID string `json:"playerID"`
|
||||
PlayerName string `json:"playerName"`
|
||||
CardCount int `json:"cardCount"`
|
||||
}, len(game.Players))
|
||||
for index, p := range game.Players {
|
||||
players[index] = struct {
|
||||
PlayerID string `json:"playerID"`
|
||||
PlayerName string `json:"playerName"`
|
||||
CardCount int `json:"cardCount"`
|
||||
}{
|
||||
PlayerID: p.ID,
|
||||
PlayerName: p.Name,
|
||||
CardCount: len(p.Hand),
|
||||
}
|
||||
}
|
||||
|
||||
game.Players = append(game.Players, player)
|
||||
|
||||
log.Println(game)
|
||||
player.send("game.join.result", map[string]interface{}{
|
||||
"hand": player.Hand,
|
||||
"playerID": player.ID,
|
||||
"activePlayerID": game.Players[game.CurrentPlayerIndex].ID,
|
||||
"players": players,
|
||||
"playingStack": game.PlayingStack.Cards,
|
||||
})
|
||||
}
|
||||
|
||||
func playCardHandler(player *Player, data map[string]interface{}) {
|
||||
log.Println("card")
|
||||
if player.Game == nil {
|
||||
return
|
||||
}
|
||||
log.Println("color")
|
||||
|
||||
if player.Game.Players[player.Game.CurrentPlayerIndex].ID != player.ID {
|
||||
return
|
||||
}
|
||||
|
||||
if player.Game.ChoosingColor {
|
||||
return
|
||||
}
|
||||
|
||||
color, ok := data["color"]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
value, ok := data["value"]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
card, ok := FindCard(player.Game.AvailableCards, color.(string), value.(string))
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
handIndex := FindOnHand(player, card)
|
||||
if handIndex == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
topCard := player.Game.PlayingStack.Cards[len(player.Game.PlayingStack.Cards)-1]
|
||||
log.Println(topCard)
|
||||
if card.Color != "BLACK" && topCard.Value != card.Value && topCard.Color != card.Color {
|
||||
return
|
||||
}
|
||||
|
||||
player.Game.PlayingStack.Cards = append(player.Game.PlayingStack.Cards, card)
|
||||
player.Hand = RemoveFromHand(player, handIndex)
|
||||
|
||||
if card.Value == "RETURN" {
|
||||
player.Game.DirectionClockwise = !player.Game.DirectionClockwise
|
||||
}
|
||||
if card.Value == "DRAW2" {
|
||||
DrawNextPlayer(player.Game, 2)
|
||||
}
|
||||
if card.Value == "DRAW4" {
|
||||
DrawNextPlayer(player.Game, 4)
|
||||
}
|
||||
|
||||
chooseColor := card.Value == "CHOOSE" || card.Value == "DRAW4"
|
||||
|
||||
if !chooseColor {
|
||||
block := card.Value == "BLOCK" || card.Value == "DRAW2"
|
||||
player.Game.nextTurn(block)
|
||||
}
|
||||
|
||||
log.Println(card)
|
||||
player.Game.send("card.played", map[string]interface{}{
|
||||
"playerID": player.ID,
|
||||
"card": &card,
|
||||
})
|
||||
|
||||
player.send("player.hand", map[string]interface{}{
|
||||
"cards": player.Hand,
|
||||
})
|
||||
|
||||
if chooseColor {
|
||||
player.Game.ChoosingColor = true
|
||||
player.send("color.choose", map[string]interface{}{})
|
||||
}
|
||||
|
||||
if !chooseColor {
|
||||
player.Game.send("turn.completed", map[string]interface{}{
|
||||
"activePlayerID": player.Game.Players[player.Game.CurrentPlayerIndex].ID,
|
||||
"directionClockwise": player.Game.DirectionClockwise,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func chooseColorHandler(player *Player, data map[string]interface{}) {
|
||||
if player.Game == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if player.Game.Players[player.Game.CurrentPlayerIndex].ID != player.ID {
|
||||
return
|
||||
}
|
||||
|
||||
if !player.Game.ChoosingColor {
|
||||
return
|
||||
}
|
||||
|
||||
color := data["color"]
|
||||
if color != "GREEN" && color != "BLUE" && color != "RED" && color != "YELLOW" {
|
||||
return
|
||||
}
|
||||
|
||||
topCard := player.Game.PlayingStack.Cards[len(player.Game.PlayingStack.Cards)-1]
|
||||
topCard.Color = color.(string)
|
||||
|
||||
player.Game.ChoosingColor = false
|
||||
player.Game.send("card.color", map[string]interface{}{
|
||||
"color": color,
|
||||
})
|
||||
|
||||
player.Game.nextTurn(topCard.Value == "DRAW4")
|
||||
|
||||
player.Game.send("turn.completed", map[string]interface{}{
|
||||
"activePlayerID": player.Game.Players[player.Game.CurrentPlayerIndex].ID,
|
||||
"directionClockwise": player.Game.DirectionClockwise,
|
||||
})
|
||||
}
|
||||
|
||||
func drawCardHandler(player *Player, data map[string]interface{}) {
|
||||
if player.Game == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if player.Game.Players[player.Game.CurrentPlayerIndex].ID != player.ID {
|
||||
return
|
||||
}
|
||||
|
||||
player.Hand = append(player.Hand, GetRandomCard(player.Game.AvailableCards))
|
||||
player.Game.nextTurn(false)
|
||||
log.Println(player.Hand)
|
||||
|
||||
player.Game.send("card.drawn", map[string]interface{}{
|
||||
"playerID": player.ID,
|
||||
"count": 1,
|
||||
})
|
||||
|
||||
player.send("player.hand", map[string]interface{}{
|
||||
"cards": player.Hand,
|
||||
})
|
||||
|
||||
player.Game.send("turn.completed", map[string]interface{}{
|
||||
"activePlayerID": player.Game.Players[player.Game.CurrentPlayerIndex].ID,
|
||||
"directionClockwise": player.Game.DirectionClockwise,
|
||||
})
|
||||
}
|
||||
|
||||
func playerLogoutHandler(player *Player) {
|
||||
delete(players, player.ID)
|
||||
}
|
||||
|
||||
func ws(w http.ResponseWriter, r *http.Request) {
|
||||
upgrader := websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
player := Player{
|
||||
ID: uuid.New().String(),
|
||||
Name: "Test Player",
|
||||
Connection: conn,
|
||||
}
|
||||
players[player.ID] = &player
|
||||
|
||||
for {
|
||||
var data DataObject
|
||||
err := conn.ReadJSON(&data)
|
||||
if err != nil {
|
||||
if websocket.IsCloseError(err, 1001) {
|
||||
log.Println("closed connection")
|
||||
playerLogoutHandler(&player)
|
||||
}
|
||||
|
||||
log.Printf("Failed to read message %v", err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
log.Println(data)
|
||||
|
||||
switch data.Type {
|
||||
case "game.init":
|
||||
gameInitHandler(&player, data.Data)
|
||||
log.Println("Initializing game")
|
||||
case "game.join":
|
||||
joinGameHandler(&player, data.Data)
|
||||
case "card.play":
|
||||
playCardHandler(&player, data.Data)
|
||||
case "card.color":
|
||||
chooseColorHandler(&player, data.Data)
|
||||
case "card.draw":
|
||||
drawCardHandler(&player, data.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
http.HandleFunc("/", ws)
|
||||
|
||||
log.Println("listening")
|
||||
err := http.ListenAndServe(*addr, nil)
|
||||
if err != nil {
|
||||
log.Fatal("listen and serve", err)
|
||||
}
|
||||
}
|
46
uno/server/models.go
Normal file
46
uno/server/models.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// DataObject is used for parsing and encoding the communication with WebSocket clients
|
||||
type DataObject struct {
|
||||
Type string `json:"type"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// Game is a model for a game instance of UNO which can be joined by users
|
||||
type Game struct {
|
||||
ID string
|
||||
MaxPlayers int
|
||||
Players []*Player
|
||||
|
||||
DirectionClockwise bool
|
||||
CurrentPlayerIndex int
|
||||
ChoosingColor bool
|
||||
|
||||
DrawingStack Stack
|
||||
PlayingStack Stack
|
||||
|
||||
AvailableCards []Card
|
||||
}
|
||||
|
||||
// Player contains data about a connected player
|
||||
type Player struct {
|
||||
ID string
|
||||
Name string
|
||||
Connection *websocket.Conn
|
||||
Game *Game
|
||||
|
||||
Hand []*Card
|
||||
}
|
||||
|
||||
type Stack struct {
|
||||
Cards []*Card
|
||||
}
|
||||
|
||||
type Card struct {
|
||||
Color string
|
||||
Value string
|
||||
}
|
Reference in New Issue
Block a user