1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package websocket.snake; 18 19 import java.io.IOException; 20 import java.util.ArrayDeque; 21 import java.util.Collection; 22 import java.util.Deque; 23 24 import jakarta.websocket.CloseReason; 25 import jakarta.websocket.CloseReason.CloseCodes; 26 import jakarta.websocket.Session; 27 28 public class Snake { 29 30 private static final int DEFAULT_LENGTH = 5; 31 32 private final int id; 33 private final Session session; 34 35 private Direction direction; 36 private int length = DEFAULT_LENGTH; 37 private Location head; 38 private final Deque<Location> tail = new ArrayDeque<>(); 39 private final String hexColor; 40 Snake(int id, Session session)41 public Snake(int id, Session session) { 42 this.id = id; 43 this.session = session; 44 this.hexColor = SnakeAnnotation.getRandomHexColor(); 45 resetState(); 46 } 47 resetState()48 private void resetState() { 49 this.direction = Direction.NONE; 50 this.head = SnakeAnnotation.getRandomLocation(); 51 this.tail.clear(); 52 this.length = DEFAULT_LENGTH; 53 } 54 kill()55 private synchronized void kill() { 56 resetState(); 57 sendMessage("{\"type\": \"dead\"}"); 58 } 59 reward()60 private synchronized void reward() { 61 length++; 62 sendMessage("{\"type\": \"kill\"}"); 63 } 64 65 sendMessage(String msg)66 protected void sendMessage(String msg) { 67 try { 68 session.getBasicRemote().sendText(msg); 69 } catch (IOException ioe) { 70 CloseReason cr = 71 new CloseReason(CloseCodes.CLOSED_ABNORMALLY, ioe.getMessage()); 72 try { 73 session.close(cr); 74 } catch (IOException ioe2) { 75 // Ignore 76 } 77 } 78 } 79 update(Collection<Snake> snakes)80 public synchronized void update(Collection<Snake> snakes) { 81 Location nextLocation = head.getAdjacentLocation(direction); 82 if (nextLocation.x >= SnakeAnnotation.PLAYFIELD_WIDTH) { 83 nextLocation.x = 0; 84 } 85 if (nextLocation.y >= SnakeAnnotation.PLAYFIELD_HEIGHT) { 86 nextLocation.y = 0; 87 } 88 if (nextLocation.x < 0) { 89 nextLocation.x = SnakeAnnotation.PLAYFIELD_WIDTH; 90 } 91 if (nextLocation.y < 0) { 92 nextLocation.y = SnakeAnnotation.PLAYFIELD_HEIGHT; 93 } 94 if (direction != Direction.NONE) { 95 tail.addFirst(head); 96 if (tail.size() > length) { 97 tail.removeLast(); 98 } 99 head = nextLocation; 100 } 101 102 handleCollisions(snakes); 103 } 104 handleCollisions(Collection<Snake> snakes)105 private void handleCollisions(Collection<Snake> snakes) { 106 for (Snake snake : snakes) { 107 boolean headCollision = id != snake.id && snake.getHead().equals(head); 108 boolean tailCollision = snake.getTail().contains(head); 109 if (headCollision || tailCollision) { 110 kill(); 111 if (id != snake.id) { 112 snake.reward(); 113 } 114 } 115 } 116 } 117 getHead()118 public synchronized Location getHead() { 119 return head; 120 } 121 getTail()122 public synchronized Collection<Location> getTail() { 123 return tail; 124 } 125 setDirection(Direction direction)126 public synchronized void setDirection(Direction direction) { 127 this.direction = direction; 128 } 129 getLocationsJson()130 public synchronized String getLocationsJson() { 131 StringBuilder sb = new StringBuilder(); 132 sb.append(String.format("{\"x\": %d, \"y\": %d}", 133 Integer.valueOf(head.x), Integer.valueOf(head.y))); 134 for (Location location : tail) { 135 sb.append(','); 136 sb.append(String.format("{\"x\": %d, \"y\": %d}", 137 Integer.valueOf(location.x), Integer.valueOf(location.y))); 138 } 139 return String.format("{\"id\":%d,\"body\":[%s]}", 140 Integer.valueOf(id), sb.toString()); 141 } 142 getId()143 public int getId() { 144 return id; 145 } 146 getHexColor()147 public String getHexColor() { 148 return hexColor; 149 } 150 } 151