Initial commit

This commit is contained in:
2020-05-05 22:35:48 +02:00
parent 3c4ec2f6dc
commit 9922e5470c
21 changed files with 2019 additions and 0 deletions

BIN
uno/server/__debug_bin Normal file

Binary file not shown.

8
uno/server/go.mod Normal file
View 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
View 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
View 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
View 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
View 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
}