Skip to content

Commit

Permalink
Look (#72)
Browse files Browse the repository at this point in the history
* gameplay

* Adds pic

* Updates spice rendering and adds new message hadnling fo rit

* updates readme

* moves collision logic to top of function
  • Loading branch information
MaxEdwards20 authored Apr 10, 2024
1 parent 1ae24ae commit b12d5b9
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 13 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dune-Snake

<!-- TODO ![Gameplay Image](./gameplay.png) -->
![Gameplay Image](./gameplay.png)

A game of Snake built using C# in the MonoGame framework, themed around everyone's favorite Sci-Fi franchise: [Dune](https://www.sfgate.com/sf-culture/article/dune-part-two-review-18678628.php).

Expand All @@ -20,12 +20,13 @@ A game of Snake built using C# in the MonoGame framework, themed around everyone
- [ ] Game over screen with score, kills, and highest position achieved. This is an overlay on the multiplayer game behind it.
- [ ] Particle system for the death of a sandworm
- [ ] Leaderboard to display top 5 players and client's score in corner of game.
- [ ] The new player should join as an invincible entity. Add this functionality and a system to update it after 3 seconds to be removed from the entity.
- [ ] Add a player status in leaderboard to show whether the player is currently invincible.
- [ ] Port Particle System - Satchel
- [ ] Message receiver for the client about collisions that can be used to call the particle system and sound effects.

## Done

- [x] Max: The new player should join as an invincible entity. Add this functionality and a system to update it after 3 seconds to be removed from the entity.
- [x] Max: On collision, sandworm breaks apart and is available as food for other snakes
- [x] Max: Grow the worm on eating food.
- [x] Satchel: Loading Screen while joining game
Expand Down
Binary file added gameplay.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/Client/GameModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ private Entity createEntity(Shared.Messages.NewEntity message)
entity.add(new Worm());
entity.add(new AnchorQueue()); // We implicitly need this because every worm part has it
}

if (message.hasInvincible)
{
entity.add(new Invincible(message.invincibleDuration));
}

if (message.hasSpicePower)
{
entity.add(new SpicePower(message.spicePower));
}

if (message.hasName)
{
Expand Down
1 change: 1 addition & 0 deletions src/Client/Menu/HighScoresView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using Client.IO;
using Shared.Components;
using Shared.Entities;
using Shared.Systems;

namespace Client.Menu
Expand Down
21 changes: 20 additions & 1 deletion src/Client/Systems/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,25 @@ private void renderEntity(TimeSpan elapsedTime, SpriteBatch spriteBatch, Entity
var size = entity.get<Shared.Components.Size>().size;
var texCenter = entity.get<Components.Sprite>().center;
var texture = entity.get<Components.Sprite>().texture;
var color = Color.White;

if (entity.contains<Invincible>())
{
var invincible = entity.get<Invincible>();
if (invincible.duration < 1000)
color = Color.Coral;
else
color = Colors.displayColor;
}

if (entity.contains<SpicePower>() && !entity.contains<Worm>())
{
var spicePower = entity.get<SpicePower>();
if (spicePower.power > 6)
color = Color.Aqua;
else if (spicePower.power > 3)
color = Color.Green;
}

// Build a rectangle centered at position, with width/height of size
Rectangle rectangle = new Rectangle(
Expand All @@ -113,7 +132,7 @@ private void renderEntity(TimeSpan elapsedTime, SpriteBatch spriteBatch, Entity
texture,
rectangle,
null,
Color.White,
color,
orientation,
texCenter,
SpriteEffects.None,
Expand Down
5 changes: 4 additions & 1 deletion src/Server/GameModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,16 @@ private void createNewWorm(int clientId, string name)
var bodySize = 80;

// Create the head
Entity? segment = WormHead.create(headStartLocation, name);
Entity segment = WormHead.create(headStartLocation, name);
segment.add(new Invincible());

// Create X number of body segments
var parent = segment;
var numToCreate = 5;
for (int i = 0; i < numToCreate; i++)
{
segment = WormSegment.create(segmentStartLocation, parent.id);
segment.add(new Invincible());
if (i == numToCreate - 1)
{
segment = WormTail.create(segmentStartLocation, parent.id);
Expand Down
16 changes: 13 additions & 3 deletions src/Server/Systems/CollisionDetection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ private void handleWormAteSpice(Entity head, Entity spice, TimeSpan elapsedTime)

private void handleWormAteWorm(List<Entity> worm, Entity otherHead)
{
if (worm[0].contains<Invincible>() || otherHead.contains<Invincible>())
{
return;
}
// Check if we hit head on head
if (otherHead.contains<Head>())
{
Expand All @@ -189,19 +193,25 @@ private void handleWormAteWorm(List<Entity> worm, Entity otherHead)
}
else
{
handleRemoveWormAndGenerateSpice(worm);
handleRemoveWormAndGenerateSpice(worm);
}
}
else // We hit the side of the worm
{
// If the worm hit the body, then the worm dies
handleRemoveWormAndGenerateSpice(worm);
if (!worm[0].contains<Invincible>())
{
handleRemoveWormAndGenerateSpice(worm);
}
}
}

private void handleWormHitWall(List<Entity> worm)
{
handleRemoveWormAndGenerateSpice(worm);
if (!worm[0].contains<Invincible>())
{
handleRemoveWormAndGenerateSpice(worm);
}
}

private void handleRemoveWormAndGenerateSpice(List<Entity> worm)
Expand Down
16 changes: 16 additions & 0 deletions src/Shared/Components/Invincible.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Shared.Components;

public class Invincible: Component
{
public int duration { get; private set; }
public Invincible(int duration = 3000)
{
this.duration = duration;
}

public void update(int elapsedTime)
{
duration -= elapsedTime;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
using System.Runtime.Serialization.Json;
using System.Threading.Tasks;

namespace Shared.Components{
namespace Shared.Entities{

[DataContract(Name = "GameScores")]
public class GameScores: Component {
public class GameScores {
[DataMember(Name = "scores")]
public List<PlayerData> scores { get; private set; }
public GameScores() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.Runtime.Serialization;

namespace Shared.Components;
namespace Shared.Entities;

[DataContract(Name = "GameScore")]
public class PlayerData: Component
public class PlayerData
{
[DataMember(Name = "date")] public DateTime date { get; private set; }
[DataMember(Name = "score")] public int score { get; private set; }
Expand Down
69 changes: 67 additions & 2 deletions src/Shared/Messages/NewEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ namespace Shared.Messages
{
public class NewEntity : Message
{
// TODO: Add a wall component check
public NewEntity(Entity entity) : base(Type.NewEntity)

Check warning on line 12 in src/Shared/Messages/NewEntity.cs

View workflow job for this annotation

GitHub Actions / buildProject

Non-nullable property 'name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
{
this.id = entity.id;
Expand Down Expand Up @@ -97,6 +96,18 @@ public NewEntity(Entity entity) : base(Type.NewEntity)
{
this.hasWall = true;
}

if (entity.contains<Shared.Components.Invincible>())
{
this.hasInvincible = true;
this.invincibleDuration = entity.get<Shared.Components.Invincible>().duration;
}

if (entity.contains<Shared.Components.SpicePower>())
{
this.hasSpicePower = true;
this.spicePower = entity.get<Shared.Components.SpicePower>().power;
}
}
public NewEntity() : base(Type.NewEntity)

Check warning on line 112 in src/Shared/Messages/NewEntity.cs

View workflow job for this annotation

GitHub Actions / buildProject

Non-nullable property 'name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
{
Expand Down Expand Up @@ -140,6 +151,14 @@ public NewEntity() : base(Type.NewEntity)
// Input
public bool hasInput { get; private set; } = false;
public List<Components.Input.Type> inputs { get; private set; }

// Invincible
public bool hasInvincible { get; private set; } = false;
public int invincibleDuration { get; private set; }

// SpicePower
public bool hasSpicePower { get; private set; } = false;
public int spicePower { get; private set; }

public override byte[] serialize()
{
Expand All @@ -162,9 +181,10 @@ public override byte[] serialize()
serializeParentId(data);
serializeChild(data);
data.AddRange(BitConverter.GetBytes(hasWorm));
serializeInvincible(data);
serializeSpicePower(data);
serializeName(data); // Make sure this is the last item to serialize


return data.ToArray();
}

Expand All @@ -190,9 +210,37 @@ public override int parse(byte[] data)
offset = parseParent(data, offset);
offset = parseChild(data, offset);
offset = parseWorm(data, offset);
offset = parseInvincible(data, offset);
offset = parseSpicePower(data, offset);
offset = parseName(data, offset); // Make sure this is the last item to parse
return offset;
}

private int parseSpicePower(byte[] data, int offset)
{
this.hasSpicePower = BitConverter.ToBoolean(data, offset);
offset += sizeof(bool);
if (hasSpicePower)
{
this.spicePower = BitConverter.ToInt32(data, offset);
offset += sizeof(int);
}

return offset;
}

private int parseInvincible(byte[] data, int offset)
{
this.hasInvincible = BitConverter.ToBoolean(data, offset);
offset += sizeof(bool);
if (hasInvincible)
{
this.invincibleDuration = BitConverter.ToInt32(data, offset);
offset += sizeof(int);
}

return offset;
}

private int parseWorm(byte[] data, int offset)
{
Expand Down Expand Up @@ -357,7 +405,24 @@ private int parseAppearance(byte[] data, int offset)

return offset;
}

private void serializeSpicePower(List<byte> data)
{
data.AddRange(BitConverter.GetBytes(hasSpicePower));
if (hasSpicePower)
{
data.AddRange(BitConverter.GetBytes(spicePower));
}
}

private void serializeInvincible(List<byte> data)
{
data.AddRange(BitConverter.GetBytes(hasInvincible));
if (hasInvincible)
{
data.AddRange(BitConverter.GetBytes(invincibleDuration));
}
}
private void serializeChild(List<byte> data)
{
data.AddRange(BitConverter.GetBytes(hasChild));
Expand Down
2 changes: 2 additions & 0 deletions src/Shared/Systems/GameScoresPersistence.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System.IO.IsolatedStorage;
using System.Runtime.Serialization.Json;
using Shared.Components;
using Shared.Entities;

namespace Shared.Systems;

public class GameScoresPersistence
{
private bool saving = false;
private bool loading = false;

private GameScores m_loadedState = new GameScores();


Expand Down
14 changes: 14 additions & 0 deletions src/Shared/Systems/WormMovement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ public override void update(TimeSpan elapsedTime)
{
applyThrust(head, elapsedTime);
}
// Update our invincibily while here
foreach (var entity in m_entities.Values)
{
if (entity.contains<Invincible>())
{
var invincible = entity.get<Invincible>();
invincible.update((int)elapsedTime.TotalMilliseconds);
if (invincible.duration <= 0)
{
entity.remove<Invincible>();
}
}
}

}

private List<Entity> getHeads()
Expand Down

0 comments on commit b12d5b9

Please sign in to comment.