Skip to content

Commit

Permalink
Merge pull request #3 from tonyzhang617/foods
Browse files Browse the repository at this point in the history
Implement food (cakes) for snakes
  • Loading branch information
tonyzhang617 committed Jul 12, 2018
2 parents 70b894b + f08aceb commit 5ca3aec
Show file tree
Hide file tree
Showing 17 changed files with 544 additions and 253 deletions.
Binary file modified android/assets/apple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added android/assets/cake.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added android/assets/ground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions core/src/com/tianyi/zhang/multiplayer/snake/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.tianyi.zhang.multiplayer.snake.agents.Client;
import com.tianyi.zhang.multiplayer.snake.agents.IAgent;
import com.tianyi.zhang.multiplayer.snake.agents.Server;
import com.tianyi.zhang.multiplayer.snake.elements.GameRenderer;
import com.tianyi.zhang.multiplayer.snake.states.GameState;
import com.tianyi.zhang.multiplayer.snake.states.client.LookForServerState;
import com.tianyi.zhang.multiplayer.snake.states.server.BroadcastState;
Expand Down Expand Up @@ -51,6 +52,7 @@ public void dispose () {
stateStack.pop().dispose();
}
VisUI.dispose();
GameRenderer.INSTANCE.dispose();
}

public void pushState(GameState gameState) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,11 @@ public class ClientSnapshot extends Snapshot {
* Makes up the last game step, guarded by stateLock
*/
private final List<Snake> snakes;
private final Foods foods;
private long stateTime;
private int nextInputId;
private final List<Input> unackInputs;

public ClientSnapshot(int clientId, long startTimestamp, List<Snake> snakes) {
this.clientId = clientId;
this.lock = new Object();
this.nextInputId = 1;
this.unackInputs = new LinkedList<Input>();
this.stateTime = 0;
this.serverUpdateVersion = new AtomicInteger(0);
this.nextRenderTime = new AtomicLong(0);
this.startTimestamp = startTimestamp;
this.snakes = new ArrayList<Snake>(snakes);

Gdx.app.debug(TAG, String.format("startTimestamp: %,d", startTimestamp));
}

public ClientSnapshot(int clientId, ServerPacket.Update initialUpdate) {
long currentNanoTime = Utils.getNanoTime();

Expand All @@ -68,6 +55,8 @@ public ClientSnapshot(int clientId, ServerPacket.Update initialUpdate) {
snakes.add(Snake.fromProtoSnake(pSnake));
}
this.snakes = new ArrayList<Snake>(snakes);
this.foods = new Foods();
this.foods.setLocations(initialUpdate.getFoodLocationsList());
}

/**
Expand Down Expand Up @@ -118,6 +107,9 @@ public void onServerUpdate(ServerPacket.Update update) {
}
}
}

List<Integer> foodLocations = update.getFoodLocationsList();
foods.setLocations(foodLocations);
}
} else {
synchronized (lock) {
Expand All @@ -130,14 +122,6 @@ public void onServerUpdate(ServerPacket.Update update) {
}
}

public Input[] getNewInputs() {
synchronized (lock) {
Input[] inputs = new Input[unackInputs.size()];
inputs = unackInputs.toArray(inputs);
return inputs;
}
}

public ClientPacket.Message buildMessage() {
ClientPacket.Message.Builder builder = ClientPacket.Message.newBuilder();
synchronized (lock) {
Expand All @@ -153,46 +137,43 @@ public ClientPacket.Message buildMessage() {
}

@Override
public Snake[] getSnakes() {
public Grid getGrid() {
long currentTime = Utils.getNanoTime() - startTimestamp;
int currentStep = (int) (currentTime / SNAKE_MOVE_EVERY_NS);

List<Snake> resultSnakes;
Foods resultFoods;
synchronized (lock) {
Snake[] resultSnakes = new Snake[snakes.size()];
for (int i = 0; i < resultSnakes.length; ++i) {
resultSnakes[i] = new Snake(snakes.get(i));
resultSnakes = new ArrayList<Snake>(snakes.size());
for (int i = 0; i < snakes.size(); ++i) {
resultSnakes.add(new Snake(snakes.get(i)));
}
resultFoods = new Foods(foods);

int stateStep = (int) (stateTime / SNAKE_MOVE_EVERY_NS);
int stepDiff = currentStep - stateStep;

int inputIndex = 0;
for (int i = 0; i <= stepDiff; ++i) {
long upper = (i == stepDiff ? currentTime : SNAKE_MOVE_EVERY_NS * (stateStep + i + 1));
for (int j = 0; j < resultSnakes.length; ++j) {
for (int j = 0; j < resultSnakes.size(); ++j) {
Snake currSnake = resultSnakes.get(j);
if (j == clientId) {
// Apply inputs
Input tmpInput;
while (unackInputs.size() > inputIndex && (tmpInput = unackInputs.get(inputIndex)).timestamp < upper) {
resultSnakes[j].handleInput(tmpInput);
currSnake.handleInput(tmpInput);
inputIndex += 1;
}
}
if (i != stepDiff) {
// Move snakes forward
resultSnakes[j].forward();
currSnake.forward();
resultFoods.consumedBy(currSnake);
}
}
}

nextRenderTime.set(SNAKE_MOVE_EVERY_NS * (currentStep + 1));

return resultSnakes;
}
}

@Override
public Grid getGrid() {
return new Grid(getSnakes(), clientId, new ArrayList<Integer>(), new ArrayList<Integer>());
return new Grid(Utils.getNanoTime(), resultSnakes, clientId, resultFoods);
}
}
110 changes: 110 additions & 0 deletions core/src/com/tianyi/zhang/multiplayer/snake/elements/Foods.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.tianyi.zhang.multiplayer.snake.elements;

import com.tianyi.zhang.multiplayer.snake.helpers.Constants;

import java.util.*;

public class Foods {
public static final int WIDTH = Constants.WIDTH;
public static final int HEIGHT = Constants.HEIGHT;

private final SortedSet<Integer> locations;
private final Random random;

public Foods() {
locations = new TreeSet<Integer>();
random = new Random(Constants.SEED);
}

public Foods(Foods other) {
locations = new TreeSet<Integer>(other.locations);
random = new Random(Constants.SEED);
}

public int getQuantity() {
return locations.size();
}

/**
*
* @param exclude an ascending sorted array of Integers to be excluded from the random generation
* @return
*/
public Integer generateHelper(List<Integer> exclude) {
Integer randomInt = Integer.valueOf(random.nextInt(HEIGHT * WIDTH - exclude.size()));
for (Integer e : exclude) {
if (randomInt.compareTo(e) < 0) {
break;
}
randomInt += 1;
}
return randomInt;
}

public void generate(List<Snake> snakes) {
List<Integer> exclude = new LinkedList<Integer>();
for (Snake s : snakes) {
List<Integer> coords = s.getCoordinates();
for (int i = 0; i < coords.size(); i += 2) {
exclude.add(indexFromXy(coords.get(i), coords.get(i+1)));
}
}
exclude.addAll(locations);
Collections.sort(exclude);
for (int i = 1; i <= Constants.MAX_FOOD_QUANTITY - getQuantity(); ++i) {
Integer newLoc = generateHelper(exclude);
locations.add(newLoc);
int size = exclude.size();
for (int j = 0; j <= size; ++j) {
if (j == size) {
exclude.add(newLoc);
} else if (newLoc < exclude.get(j)) {
exclude.add(j, newLoc);
break;
}
}
}
}

public void consumedBy(Snake snake) {
Integer headIndex = indexFromXy(snake.getHeadX(), snake.getHeadY());
if (locations.contains(headIndex)) {
locations.remove(headIndex);
snake.grow();
}
}

public boolean shouldGenerate() {
return getQuantity() <= Constants.MIN_FOOD_QUANTITY;
}

public Integer indexFromXy(Integer x, Integer y) {
return Integer.valueOf(y*WIDTH + x);
}

public Integer xFromIndex(int index) {
return index - index / WIDTH * WIDTH;
}

public int yFromIndex(int index) {
return index / WIDTH;
}

public List<Integer> getLocations() {
List<Integer> result = new ArrayList<Integer>(locations.size() * 2);
Iterator<Integer> iterator = locations.iterator();
while (iterator.hasNext()) {
Integer index = iterator.next();
result.add(Integer.valueOf(xFromIndex(index)));
result.add(Integer.valueOf(yFromIndex(index)));
}
return Collections.unmodifiableList(result);
}

public void setLocations(List<Integer> xyLocations) {
this.locations.clear();
for (int i = 0; i < xyLocations.size(); i += 2) {
this.locations.add(indexFromXy(xyLocations.get(i), xyLocations.get(i+1)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.tianyi.zhang.multiplayer.snake.elements;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.tianyi.zhang.multiplayer.snake.helpers.Constants;

import java.util.HashMap;
import java.util.Map;

import static com.tianyi.zhang.multiplayer.snake.elements.Grid.Block.FOOD;
import static com.tianyi.zhang.multiplayer.snake.elements.Grid.Block.GROUND;
import static com.tianyi.zhang.multiplayer.snake.elements.Grid.Block.PLAYER_SNAKE_BODY;
import static com.tianyi.zhang.multiplayer.snake.helpers.Constants.BLOCK_LENGTH;
import static com.tianyi.zhang.multiplayer.snake.helpers.Constants.BLOCK_OFFSET;

public enum GameRenderer {
INSTANCE;

private Map<Grid.Block, Sprite> spriteMap;
private SpriteBatch batch;

private GameRenderer() {
Sprite playerSnakeBody = new Sprite(newTextureWithLinearFilter("player_snake_body.png"));
Sprite food = new Sprite(newTextureWithLinearFilter("cake.png"));
Sprite ground = new Sprite(newTextureWithLinearFilter("ground.png"));

spriteMap = new HashMap<Grid.Block, Sprite>();
spriteMap.put(PLAYER_SNAKE_BODY, playerSnakeBody);
spriteMap.put(FOOD, food);
spriteMap.put(GROUND, ground);

batch = new SpriteBatch();
}

private static Texture newTextureWithLinearFilter(String imagePath) {
Texture texture = new Texture(Gdx.files.internal(imagePath));
texture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
return texture;
}

public void clear() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}

public void render(Grid grid, Camera camera) {
camera.update();

batch.setProjectionMatrix(camera.combined);
batch.begin();

for (int y = Grid.HEIGHT - 1; y >= 0; --y) {
for (int x = 0; x < Grid.WIDTH; ++x) {
switch (grid.getBlockByCoordinate(x, y)) {
case PLAYER_SNAKE_BODY:
case SNAKE_BODY:
batch.draw(spriteMap.get(PLAYER_SNAKE_BODY), x - Constants.BLOCK_OFFSET, y, BLOCK_LENGTH + BLOCK_OFFSET, BLOCK_LENGTH + BLOCK_OFFSET);
break;
case FOOD:
batch.draw(spriteMap.get(GROUND), x, y, BLOCK_LENGTH, BLOCK_LENGTH);
batch.draw(spriteMap.get(FOOD), x - Constants.BLOCK_OFFSET, y, BLOCK_LENGTH + BLOCK_OFFSET, BLOCK_LENGTH + BLOCK_OFFSET);
break;
case GROUND:
batch.draw(spriteMap.get(GROUND), x, y, BLOCK_LENGTH, BLOCK_LENGTH);
break;
}
}
}

batch.end();
}

public void dispose() {
batch.dispose();
}
}
25 changes: 20 additions & 5 deletions core/src/com/tianyi/zhang/multiplayer/snake/elements/Grid.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ public class Grid {
public static final int WIDTH = Constants.WIDTH;
public static final int HEIGHT = Constants.HEIGHT;

public final long timestamp;
public final List<Snake> snakes;
public final Foods foods;

public enum Block {
PLAYER_SNAKE_BODY, SNAKE_BODY, CRATE, FOOD, GROUND
}

private final List<List<Block>> blocks;

public Grid(Snake[] snakes, int playerIndex, List<Integer> foods, List<Integer> obstacles) {
public Grid(long timestamp, List<Snake> snakes, int playerIndex, Foods foods) {
this.timestamp = timestamp;
this.snakes = snakes;
this.foods = foods;

blocks = new ArrayList<List<Block>>(HEIGHT);
for (int i = 0; i < HEIGHT; ++i) {
blocks.add(new ArrayList<Block>(WIDTH));
Expand All @@ -24,24 +32,31 @@ public Grid(Snake[] snakes, int playerIndex, List<Integer> foods, List<Integer>
}
}

for (int i = 0; i < snakes.length; ++i) {
List<Integer> coords = snakes[i].getCoordinates();
List<Integer> coords;
for (int i = 0; i < snakes.size(); ++i) {
coords = snakes.get(i).getCoordinates();
if (i == playerIndex) {
for (int j = 0; j < coords.size(); j += 2) {
int x = coords.get(j), y = coords.get(j+1);
if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) {
blocks.get(coords.get(j + 1)).set(coords.get(j), Block.PLAYER_SNAKE_BODY);
blocks.get(y).set(x, Block.PLAYER_SNAKE_BODY);
}
}
} else {
for (int j = 0; j < coords.size(); j += 2) {
int x = coords.get(j), y = coords.get(j+1);
if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) {
blocks.get(coords.get(j + 1)).set(coords.get(j), Block.SNAKE_BODY);
blocks.get(y).set(x, Block.SNAKE_BODY);
}
}
}
}

coords = foods.getLocations();
for (int i = 0; i < coords.size(); i += 2) {
int x = coords.get(i), y = coords.get(i+1);
blocks.get(y).set(x, Block.FOOD);
}
}

public Block getBlockByCoordinate(int x, int y) {
Expand Down
Loading

0 comments on commit 5ca3aec

Please sign in to comment.