initial commit
This commit is contained in:
commit
ac9aa86d33
14 changed files with 1021 additions and 0 deletions
306
cmd/go-fish/main.go
Normal file
306
cmd/go-fish/main.go
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
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))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue