306 lines
6.6 KiB
Go
306 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"text/template"
|
|
|
|
"github.com/danyel/go-fish/internal/game"
|
|
"github.com/danyel/go-fish/internal/store"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
)
|
|
|
|
type JoinResponse struct {
|
|
PlayerID string `json:"playerId"`
|
|
}
|
|
|
|
type AskRequest struct {
|
|
PlayerID string `json:"playerId"`
|
|
OpponentID string `json:"opponentId"`
|
|
Rank string `json:"rank"`
|
|
}
|
|
|
|
type AskResponse struct {
|
|
Status string `json:"status"`
|
|
MyHand []string `json:"myHand"`
|
|
MyBookRanks []string `json:"myBookRanks"`
|
|
OpponentBookRanks []string `json:"opponentBookRanks"`
|
|
GameOver bool `json:"gameOver"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func main() {
|
|
s := store.NewPlayerStore()
|
|
|
|
mux := chi.NewMux()
|
|
mux.Use(middleware.Logger)
|
|
mux.Use(middleware.Recoverer)
|
|
|
|
mux.Get("/", homeHandler)
|
|
mux.Get("/waiting", waitingHandler(s))
|
|
mux.Get("/play/{id}", playHandler(s))
|
|
mux.Post("/join", joinHandler(s))
|
|
mux.Post("/ask", askHandler(s))
|
|
|
|
fmt.Printf("Starting go-fish on :3000\n")
|
|
http.ListenAndServe(":3000", mux)
|
|
}
|
|
|
|
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
|
tmpl := template.Must(template.ParseFiles("web/templates/index.html"))
|
|
tmpl.Execute(w, nil)
|
|
}
|
|
|
|
func waitingHandler(s *store.PlayerStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
players := s.GetAvailablePlayers()
|
|
hasBotOption := len(players) <= 1
|
|
tmpl := template.Must(template.ParseFiles("web/templates/waiting.html"))
|
|
data := struct {
|
|
Players []*game.Player
|
|
HasBotOption bool
|
|
}{Players: players, HasBotOption: hasBotOption}
|
|
tmpl.Execute(w, data)
|
|
}
|
|
}
|
|
|
|
func playHandler(s *store.PlayerStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
_ = id
|
|
p, ok := s.GetPlayer(id)
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
available := s.GetAvailablePlayers()
|
|
var opponent *game.Player
|
|
for _, ap := range available {
|
|
if ap.ID != p.ID {
|
|
opponent = ap
|
|
break
|
|
}
|
|
}
|
|
|
|
var isBot bool
|
|
var g *game.Game
|
|
|
|
if opponent == nil && len(available) == 1 {
|
|
isBot = true
|
|
botID := generateID()
|
|
bot := game.NewPlayer(botID, "Bot")
|
|
s.AddPlayer(botID, "Bot")
|
|
opponent = bot
|
|
s.MarkPlaying(p.ID)
|
|
s.MarkPlaying(botID)
|
|
g = game.NewGame(p, opponent)
|
|
g.ID = "game-" + p.ID + "-" + botID
|
|
} else if opponent != nil {
|
|
isBot = false
|
|
s.MarkPlaying(p.ID)
|
|
s.MarkPlaying(opponent.ID)
|
|
g = game.NewGame(p, opponent)
|
|
g.ID = "game-" + p.ID + "-" + opponent.ID
|
|
} else {
|
|
isBot = true
|
|
botID := generateID()
|
|
bot := game.NewPlayer(botID, "Bot")
|
|
s.AddPlayer(botID, "Bot")
|
|
opponent = bot
|
|
s.MarkPlaying(p.ID)
|
|
s.MarkPlaying(botID)
|
|
g = game.NewGame(p, opponent)
|
|
g.ID = "game-" + p.ID + "-" + botID
|
|
}
|
|
|
|
s.CreateGame(p.ID, g)
|
|
|
|
for i := 0; i < 7; i++ {
|
|
if len(g.Deck) > 0 {
|
|
g.Player1.Draw(g.DrawCard())
|
|
g.Player2.Draw(g.DrawCard())
|
|
}
|
|
}
|
|
|
|
tmpl := template.Must(template.ParseFiles("web/templates/play.html"))
|
|
data := struct {
|
|
Game *game.Game
|
|
Player *game.Player
|
|
IsBot bool
|
|
}{Game: g, Player: p, IsBot: isBot}
|
|
tmpl.Execute(w, data)
|
|
}
|
|
}
|
|
|
|
func joinHandler(s *store.PlayerStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := r.FormValue("name")
|
|
if name == "" {
|
|
name = "Player"
|
|
}
|
|
id := generateID()
|
|
s.AddPlayer(id, name)
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: "player_id",
|
|
Value: id,
|
|
Path: "/",
|
|
})
|
|
respondJSON(w, http.StatusOK, JoinResponse{PlayerID: id})
|
|
}
|
|
}
|
|
|
|
func askHandler(s *store.PlayerStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req AskRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
g, ok := s.GetGame(req.PlayerID)
|
|
if !ok {
|
|
respondJSON(w, http.StatusNotFound, AskResponse{Message: "Game not found"})
|
|
return
|
|
}
|
|
|
|
rank := game.RankAce
|
|
switch req.Rank {
|
|
case "A":
|
|
rank = game.RankAce
|
|
case "2":
|
|
rank = game.Rank2
|
|
case "3":
|
|
rank = game.Rank3
|
|
case "4":
|
|
rank = game.Rank4
|
|
case "5":
|
|
rank = game.Rank5
|
|
case "6":
|
|
rank = game.Rank6
|
|
case "7":
|
|
rank = game.Rank7
|
|
case "8":
|
|
rank = game.Rank8
|
|
case "9":
|
|
rank = game.Rank9
|
|
case "10":
|
|
rank = game.Rank10
|
|
case "J":
|
|
rank = game.RankJack
|
|
case "Q":
|
|
rank = game.RankQueen
|
|
case "K":
|
|
rank = game.RankKing
|
|
}
|
|
|
|
asker := g.Player1
|
|
target := g.Player2
|
|
if g.Current == 2 {
|
|
asker = g.Player2
|
|
target = g.Player1
|
|
}
|
|
|
|
var cardsRemoved []game.Card
|
|
|
|
if !asker.HasCard(rank) {
|
|
card := g.DrawCard()
|
|
target.Draw(card)
|
|
} else {
|
|
cardsRemoved = asker.RemoveCard(rank)
|
|
if len(cardsRemoved) > 0 {
|
|
asker.Hand = append(asker.Hand, cardsRemoved...)
|
|
if asker.HasBook(rank) {
|
|
g.BookPile = append(g.BookPile, []game.Card{})
|
|
}
|
|
}
|
|
}
|
|
|
|
gameOver := false
|
|
message := ""
|
|
|
|
myHand := asker.Hand
|
|
oppHand := target.Hand
|
|
|
|
myBookCount := countBooks(g.BookPile)
|
|
oppBookCount := countOpponentBooks(g.BookPile)
|
|
|
|
if len(myHand) == 0 && len(oppHand) == 0 {
|
|
gameOver = true
|
|
message = "Game over! No more cards."
|
|
}
|
|
|
|
if myBookCount >= 13 || oppBookCount >= 13 {
|
|
gameOver = true
|
|
if myBookCount >= 13 && oppBookCount >= 13 {
|
|
message = "Draw! Both players have 13 books."
|
|
} else if myBookCount >= 13 {
|
|
message = "You win! You have 13 books."
|
|
} else {
|
|
message = "You lose! Opponent has 13 books."
|
|
}
|
|
}
|
|
|
|
s.CreateGame(req.PlayerID, g)
|
|
|
|
respondJSON(w, http.StatusOK, AskResponse{
|
|
Status: "success",
|
|
MyHand: cardStrings(myHand),
|
|
MyBookRanks: bookRanks(g.BookPile),
|
|
OpponentBookRanks: bookRanks(g.BookPile),
|
|
GameOver: gameOver,
|
|
Message: message,
|
|
})
|
|
}
|
|
}
|
|
|
|
func countBooks(books [][]game.Card) int {
|
|
count := 0
|
|
for _, book := range books {
|
|
if len(book) > 0 {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func countOpponentBooks(books [][]game.Card) int {
|
|
count := 0
|
|
for _, book := range books {
|
|
if len(book) > 0 {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func bookRanks(books [][]game.Card) []string {
|
|
var ranks []string
|
|
for _, book := range books {
|
|
if len(book) > 0 {
|
|
ranks = append(ranks, book[0].Rank.String())
|
|
}
|
|
}
|
|
return ranks
|
|
}
|
|
|
|
func cardStrings(cards []game.Card) []string {
|
|
var result []string
|
|
for _, c := range cards {
|
|
result = append(result, c.Rank.String()+c.Suit.String())
|
|
}
|
|
return result
|
|
}
|
|
|
|
func respondJSON(w http.ResponseWriter, code int, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(code)
|
|
json.NewEncoder(w).Encode(data)
|
|
}
|
|
|
|
func generateID() string {
|
|
return fmt.Sprintf("player-%d", rand.Intn(10000))
|
|
}
|