diff --git a/assets/javascript/game.js b/assets/javascript/game.js index e57e1a1..789c9a0 100644 --- a/assets/javascript/game.js +++ b/assets/javascript/game.js @@ -25,14 +25,23 @@ window.Game = React.createClass({ } $.get('/game/' + this.props.gameID, (data) => { - if (this.state.game && data.created_at != this.state.game.created_at) { - this.setState({codemaster: false}); - } + if (this.state.game) { + if (data.round != this.state.game.round) { + this.changedTurn(); + } + if (data.created_at != this.state.game.created_at) { + this.setState({codemaster: false}); + } + } this.setState({game: data}); setTimeout(this.refresh, 3000); }); }, + changedTurn: function() { + this.setState({hasClue: false}); + }, + toggleRole: function(e, role) { e.preventDefault(); this.setState({codemaster: role=='codemaster'}); @@ -77,7 +86,7 @@ window.Game = React.createClass({ endTurn: function() { $.post('/end-turn', JSON.stringify({game_id: this.state.game.id}), - (g) => { this.setState({game: g}); }); + (g) => { this.setState({game: g}); this.changedTurn(); }); }, nextGame: function(e) { @@ -86,6 +95,19 @@ window.Game = React.createClass({ (g) => { this.setState({game: g, codemaster: false}) }); }, + giveClue: function(e) { + let clueWord = $('#clue-word')[0].value; + let clueCount = parseInt($('#clue-count')[0].value); + // How does this not have an error callback :( + $.post('/clue', JSON.stringify({ + game_id: this.state.game.id, + word: clueWord, + count: clueCount + }), (g) => { + this.setState({game: g, hasClue: true}); + }); + }, + render: function() { if (!this.state.game) { return (

Loading…

); @@ -110,6 +132,23 @@ window.Game = React.createClass({ otherTeam = 'red'; } + let clueDOM; + if (this.state.codemaster) { + clueDOM = ( +
+ + + +
+ ) + } else { + clueDOM = ( +
+ {this.state.game.clue ? "Clue: " + this.state.game.clue.word + "(" + this.state.game.clue.count + ")" : "Waiting for clue..."} +
+ ); + } + return (
@@ -118,6 +157,7 @@ window.Game = React.createClass({
{status}
+ {clueDOM}
{this.remaining(this.state.game.starting_team)} diff --git a/assets/stylesheets/game.css b/assets/stylesheets/game.css index 2ded431..8e5b01d 100644 --- a/assets/stylesheets/game.css +++ b/assets/stylesheets/game.css @@ -26,6 +26,8 @@ font-weight: bold; } +.clue-line { text-align: center; margin-bottom: 1em; } + #remaining { float: left; } #remaining .red-remaining { color: #D13030; } #remaining .blue-remaining { color: #4183CC; } diff --git a/game.go b/game.go index f496896..b0ccd71 100644 --- a/game.go +++ b/game.go @@ -56,6 +56,11 @@ func (t Team) Repeat(n int) []Team { return s } +type Clue struct { + Word string `json:"word"` + Count int `json:"count"` +} + type Game struct { ID string `json:"id"` CreatedAt time.Time `json:"created_at"` @@ -65,6 +70,7 @@ type Game struct { Words []string `json:"words"` Layout []Team `json:"layout"` Revealed []bool `json:"revealed"` + Clue *Clue `json:"clue"` } func (g *Game) checkWinningCondition() { @@ -98,6 +104,7 @@ func (g *Game) NextTurn() error { return errors.New("game is already over") } g.Round++ + g.Clue = nil return nil } @@ -119,6 +126,7 @@ func (g *Game) Guess(idx int) error { g.checkWinningCondition() if g.Layout[idx] != g.CurrentTeam() { g.Round = g.Round + 1 + g.Clue = nil } return nil } @@ -130,6 +138,13 @@ func (g *Game) CurrentTeam() Team { return g.StartingTeam.Other() } +func (g *Game) AddClue(word string, count int) { + g.Clue = &Clue{ + Word: word, + Count: count, + } +} + func newGame(id string, words []string) *Game { game := &Game{ ID: id, diff --git a/server.go b/server.go index 899325a..ddeb758 100644 --- a/server.go +++ b/server.go @@ -6,6 +6,7 @@ import ( "html/template" "net/http" "path" + "regexp" "sync" "time" @@ -13,6 +14,8 @@ import ( "github.com/jbowens/dictionary" ) +var validClueRegex *regexp.Regexp + type Server struct { Server http.Server @@ -120,6 +123,38 @@ func (s *Server) handleNextGame(rw http.ResponseWriter, req *http.Request) { writeJSON(rw, g) } +// POST /clue +func (s *Server) handleClue(rw http.ResponseWriter, req *http.Request) { + var request struct { + GameID string `json:"game_id"` + Word string `json:"word"` + Count int `json:"count"` + } + + decoder := json.NewDecoder(req.Body) + if err := decoder.Decode(&request); err != nil { + http.Error(rw, "Error decoding", 400) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + g, ok := s.games[request.GameID] + if !ok { + http.Error(rw, "No such game", 404) + return + } + + if ok := validClueRegex.MatchString(request.Word); !ok { + http.Error(rw, "not a valid clue", 400) + return + } + + g.AddClue(request.Word, request.Count) + writeJSON(rw, g) +} + func (s *Server) cleanupOldGames() { s.mu.Lock() defer s.mu.Unlock() @@ -138,6 +173,8 @@ func (s *Server) cleanupOldGames() { } func (s *Server) Start() error { + validClueRegex, _ = regexp.Compile(`^[A-Za-z]+$`) + d, err := dictionary.Load("assets/original.txt") if err != nil { return err @@ -164,6 +201,7 @@ func (s *Server) Start() error { s.mux.HandleFunc("/next-game", s.handleNextGame) s.mux.HandleFunc("/end-turn", s.handleEndTurn) s.mux.HandleFunc("/guess", s.handleGuess) + s.mux.HandleFunc("/clue", s.handleClue) s.mux.HandleFunc("/game/", s.handleRetrieveGame) s.mux.Handle("/js/lib/", http.StripPrefix("/js/lib/", s.jslib))