diff --git a/.gitignore b/.gitignore index 58e2c9d..9552a4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea snakeserver +.vscode +__debug_bin \ No newline at end of file diff --git a/main.go b/main.go index d4a671b..0f95e23 100644 --- a/main.go +++ b/main.go @@ -16,20 +16,35 @@ const pixels = 300 const width = 20 const defaultTTL = 100 // inactivity for this many game loops kills the session +type fruit struct { + position int + placementWaitCycle int + maxPlacementWaitCycle int + consumed bool + variant byte + points int +} + type session struct { snek []int currentDirection string - fruit int token string randomizer *rand.Rand ttl int + fruits []fruit + totalPoints int } -type State struct { +type state struct { Status string `json:"status"` Token string `json:"token"` } +type gameState struct { + Board string `json:"board"` + Points int `json:"points"` +} + type server struct { session *session } @@ -48,9 +63,36 @@ func main() { s := server{} mux := http.NewServeMux() + + mux.HandleFunc("/gamestate", func(w http.ResponseWriter, r *http.Request) { + setCors(&w) + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte("method not supported")) + return + } + board, err := s.getBoard() + if err != nil { + // error means no board means game over + log.Printf("err %v", err) + w.WriteHeader(http.StatusNotFound) + w.Write([]byte(err.Error())) + } + + points := s.session.totalPoints + gameState := gameState{ + Board: string(board), + Points: points, + } + + if err := json.NewEncoder(w).Encode(&gameState); err != nil { + log.Println(err) + } + }) + mux.HandleFunc("/state", func(w http.ResponseWriter, r *http.Request) { setCors(&w) - currentState := State{} + currentState := state{} if s.session != nil { currentState.Status = "playing" } @@ -146,17 +188,30 @@ func main() { func newSession() *session { randomizer := makeRandomizer() snek := []int{112, 111, 110} - f := placeFruit(snek, randomizer) token := uuid.NewV4().String() + fruits := []fruit{ + fruit{ + position: placeFruit(snek, randomizer), + maxPlacementWaitCycle: 10, + points: 10, + variant: '2', + }, + fruit{ + position: placeFruit(snek, randomizer), + maxPlacementWaitCycle: 15, + points: 20, + variant: '3', + }, + } sess := session{ snek: snek, currentDirection: "R", - fruit: f, token: token, randomizer: randomizer, ttl: defaultTTL, + fruits: fruits, } log.Printf("created new session %#v", sess) return &sess @@ -167,7 +222,7 @@ func (s *server) getBoard() ([]byte, error) { return nil, errors.New("game over") } - b := boardAsBytes(s.session.snek, s.session.fruit) + b := boardAsBytes(s.session.snek, s.session.fruits) return b, nil } @@ -177,8 +232,7 @@ func (s *server) input(cmd string) ([]byte, error) { return nil, err } - b := boardAsBytes(s.session.snek, s.session.fruit) - + b := boardAsBytes(s.session.snek, s.session.fruits) return b, nil } @@ -214,7 +268,7 @@ func (s *server) updateBoard(cmd string) error { return nil } -func boardAsBytes(snek []int, fruit int) []byte { +func boardAsBytes(snek []int, fruits []fruit) []byte { b := make([]byte, pixels) for i := range b { b[i] = byte('0') @@ -222,8 +276,11 @@ func boardAsBytes(snek []int, fruit int) []byte { for _, s := range snek { b[s] = byte('1') } - if fruit >= 0 { - b[fruit] = byte('2') + + for _, fruit := range fruits { + if !fruit.consumed { + b[fruit.position] = fruit.variant + } } return b @@ -253,9 +310,17 @@ func placeFruit(snek []int, r *rand.Rand) int { } } +func consumedFruit(snekHeadPosition int, fruits []fruit) (bool, int) { + for i := range fruits { + if snekHeadPosition == fruits[i].position { + return true, i + } + } + return false, -1 +} + func gameLoop(s *server) { t := time.NewTicker(time.Millisecond * 500) - var waitCyclesToPlaceFruit int for { select { case <-t.C: @@ -270,27 +335,31 @@ func gameLoop(s *server) { continue } - // Keep the last snake pixel if it ate a fruit - if s.session.fruit == snake[0] { - log.Printf("snake ate fruit at %d", snake[0]) + consumed, fruitIdx := consumedFruit(snake[0], s.session.fruits) + if consumed { + log.Printf("snake comsumed fruit at %d", snake[0]) + fruit := s.session.fruits[fruitIdx] + + fruit.consumed = true + fruit.placementWaitCycle = s.session.randomizer.Intn(fruit.maxPlacementWaitCycle) + 1 + fruit.position = placeFruit(snake, s.session.randomizer) + + s.session.fruits[fruitIdx] = fruit + s.session.totalPoints += fruit.points snake = append(snake, s.session.snek[len(s.session.snek)-1]) - s.session.fruit = -1 // delete the fruit - waitCyclesToPlaceFruit = s.session.randomizer.Intn(10) + 1 - log.Printf("wait for %d cycles to place new fruit", waitCyclesToPlaceFruit) } - s.session.snek = snake - - // count down to place new fruit - if s.session.fruit == -1 { - log.Printf("there is no fruit") - waitCyclesToPlaceFruit-- - if waitCyclesToPlaceFruit == 0 { - s.session.fruit = placeFruit(snake, s.session.randomizer) - log.Printf("dropped fruit at %d", s.session.fruit) + for i, fruit := range s.session.fruits { + if fruit.consumed { + s.session.fruits[i].placementWaitCycle-- + if fruit.placementWaitCycle == 0 { + s.session.fruits[i].consumed = false + } } } + s.session.snek = snake + s.session.ttl-- if s.session.ttl == 0 { log.Println("zombie game - killing it") diff --git a/main_test.go b/main_test.go index 90e9c90..e2b0b4e 100644 --- a/main_test.go +++ b/main_test.go @@ -75,6 +75,47 @@ func TestCalculateNextPixel(t *testing.T) { } } +func Test_consumedFruit(t *testing.T) { + consumeAtIndex := 1 + fruits := []fruit{ + fruit{ + position: consumeAtIndex, + }, + } + + type arg struct { + snekHeadPosition int + fruits []fruit + } + + tests := []struct { + name string + args arg + want bool + }{ + { + name: "consumed fruit if head at fruit", + want: true, + args: arg{ + snekHeadPosition: consumeAtIndex, + }, + }, + { + name: "did not fruit if head at not fruit", + want: false, + args: arg{ + snekHeadPosition: 999, + }, + }, + } + + for _, tt := range tests { + if got, _ := consumedFruit(tt.args.snekHeadPosition, fruits); got != tt.want { + t.Errorf("consumedFruit() = %v, want %v", got, tt.want) + } + } +} + func Test_moveMotherfuckingSnake(t *testing.T) { type args struct { snake []int @@ -136,7 +177,7 @@ func Test_moveMotherfuckingSnake(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := moveMotherfuckingSnake(tt.args.snake, tt.args.direction); !reflect.DeepEqual(got, tt.want) { + if got, _ := moveMotherfuckingSnake(tt.args.snake, tt.args.direction); !reflect.DeepEqual(got, tt.want) { t.Errorf("moveMotherfuckingSnake() = %v, want %v", got, tt.want) } })