Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple fruits with different points and respawn rates #1

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.idea
snakeserver
.vscode
__debug_bin
123 changes: 96 additions & 27 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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"
}
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -214,16 +268,19 @@ 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')
}
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
Expand Down Expand Up @@ -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:
Expand All @@ -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")
Expand Down
43 changes: 42 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
})
Expand Down