Compare commits

...

No commits in common. "Matrix-1.0.0" and "rpi-rgb-led-matrix" have entirely different histories.

2 changed files with 178 additions and 409 deletions

View File

@ -1,395 +1,241 @@
using System.Collections.Generic;
using System.Net.Sockets;
using System.Diagnostics;
using System.Threading;
using System;
using SocketLib;
using rpi_rgb_led_matrix_sharp;
namespace SnakeGame {
class SnakeGame {
private static int width = 32;
private static int height = 32;
private static string lobbyCode = "";
public static class SnakeGame {
public static int baseLength = 5;
private static Segment food = new();
private static List<Snake> snakes = new();
private static RGBLedMatrix? _matrix;
private static RGBLedCanvas? _canvas;
public static bool gameOver, selection;
private static int FPS = 10, timeout = 10;
private static int frameCount = 0, keypressFrame = 0;
private static Snake self = new(width/2, height/2);
private static bool gameOver, isReady, isMultiplayer, isPlaying = false;
public static List<string> players = new();
public static int width = 48, height = 48, sw = 4, sh = 4;
public static int frameCount, keypressFrame, selectionIndex;
public static Dictionary<string, Color> colors = new() {{"bot", new(0, 255, 0)}};
public static Dictionary<string, Snake> snakes = new() {{"bot", new(width / 2, height / 2)}};
private static void Main() {
// Calculate the number of milliseconds in which to display 1 frame
int frameMills = 1000/FPS;
// Decide on Single- or Multiplayer version
Console.Clear();
while (true) {
Console.Clear();
Console.WriteLine("################################");
Console.WriteLine("Art des Spielens:");
Console.WriteLine("0) Schließen");
Console.WriteLine("1) Singleplayer");
Console.WriteLine("2) Multiplayer");
Console.WriteLine("################################");
Console.WriteLine();
Console.Write("Wahl (0-2): ");
string input = Console.ReadLine();
// Handle user input with switch statement
bool ready = true;
switch (input) {
default:
// Invalid input was given
Console.Write("Ungültige Eingabe oder Fehler. Bitte erneut versuchen. ");
Console.ReadKey(true);
ready = false;
break;
case "0":
// Exit program
Environment.Exit(0);
break;
case "1":
// Play as Singleplayer: No more operatiosn required
break;
case "2":
// Redirect to multiplayer options, reset if multiplayer wasn't successfully started
ready = multiplayerConnect();
break;
public static void Init(RGBLedMatrix matrix, RGBLedCanvas canvas) {
_matrix = matrix;
_canvas = canvas;
}
public static void Main(string[] args) {
// Initialize snakes
foreach (string p in players)
if (!snakes.ContainsKey(p)) {
snakes.Add(p, new(width / 2, height / 2));
Random rng = new();
colors.Add(p, new(
rng.Next(256),
rng.Next(256),
rng.Next(256)));
}
// Only break out of while-Loop on successful selection
if (ready)
foreach (KeyValuePair<string, Snake> kvp in snakes)
if (kvp.Key != "bot" && !players.Contains(kvp.Key)) {
snakes.Remove(kvp.Key);
colors.Remove(kvp.Key);
}
// Initialize Variables
gameOver = false;
width = 192 / sw;
height = 192 / sh;
// Check CL_Args for parameters
foreach (string arg in args)
if (arg == "-b" || arg == "--bot") {
keypressFrame = -timeout * FPS;
break;
}
}
// Calculate the number of milliseconds in which to display 1 frame
int frameMills = 1000 / FPS;
// Console Output Setup
Console.Clear();
if (isMultiplayer) {
Console.SetCursorPosition(width/2, 0);
Console.ForegroundColor = ConsoleColor.White;
Console.Write($"Lobby Code: {lobbyCode}");
// Notify Server when player is ready
Connection.Send(Connection.self, "PlayerReady");
isReady = true;
}
// Initialize RGBLedMatrix
RGBLedMatrix matrix = new(
new RGBLedMatrixOptions {
HardwareMapping = "adafruit-hat",
LimitRefreshRateHz = 0,
GpioSlowdown = 4,
Brightness = 100,
ChainLength = 1,
ScanMode = 1,
Rows = 64,
Cols = 64,
});
RGBLedCanvas canvas = matrix.CreateOffscreenCanvas();
RGBLedMatrix matrix;
RGBLedCanvas canvas;
if (_matrix != null && _canvas != null) {
matrix = _matrix;
canvas = _canvas;
}
else {
matrix = new(
new RGBLedMatrixOptions {
GpioSlowdown = 5,
ChainLength = 3,
Parallel = 3,
Rows = 64,
Cols = 64,
});
canvas = matrix.CreateOffscreenCanvas();
}
// Initialize stopwatch to measure calculation and display time
Stopwatch stopwatch = new();
// Game Play
gameOver = false;
while (!gameOver) {
// Start stopwatch
stopwatch.Restart();
// Handle key presses and exiting
self.keyHandler();
// Clear canvas
canvas.Clear();
// If player is absent, automatically steer sneak towards food (very simple)
if (frameCount >= keypressFrame+timeout*FPS) {
Snake self = snakes["bot"];
if (frameCount >= keypressFrame + timeout * FPS) {
if (food.x < self.ownPos.x && self.mx != 1) {
// Steer snake towards food on its left side
self.mx = -1;
self.my = 0;
} else if (food.x > self.ownPos.x && self.mx != -1) {
// Steer snake towards food on its right side
self.mx = 1;
self.my = 0;
} else if (food.y < self.ownPos.y && self.my != 1) {
// Steer sake towards food below it
self.mx = 0;
self.my = -1;
} else if (food.y > self.ownPos.y && self.my != -1) {
// Steer snake towards food above it
self.mx = 0;
self.my = 1;
self.my = 0;
}
}
// Handle snake movement
self.move();
// Handle snake dying
self.touchHandler(self);
if (isMultiplayer)
for (int i = 0; i < snakes.Count; i++)
self.touchHandler(snakes[i]);
// Handle snake eating food
self.foodHandler(food);
// Display own Snake
canvas.Clear();
Color green = new(0, 255, 0);
setSnakePixels(canvas, self, green);
// Display other Snakes
Color blue = new(0, 127, 255);
for (int i = 0; i < snakes.Count; i++) {
Snake current = snakes[i];
setSnakePixels(canvas, current, blue);
}
else if (food.x > self.ownPos.x && self.mx != -1) {
// Steer snake towards food on its right side
self.mx = 1;
self.my = 0;
}
else if (food.y < self.ownPos.y && self.my != 1) {
// Steer sake towards food below it
self.mx = 0;
self.my = -1;
}
else if (food.y > self.ownPos.y && self.my != -1) {
// Steer snake towards food above it
self.mx = 0;
self.my = 1;
}
// Move bot around
self.move();
self.touchHandler(self);
self.foodHandler(food);
setSnakePixels(canvas, self, colors["bot"]);
// Player logic
} else
foreach (KeyValuePair<string, Snake> kvp in snakes) {
if (kvp.Key == "bot")
continue;
Snake current = kvp.Value;
// Handle snake movement
current.move();
// Handle snake dying
foreach (KeyValuePair<string, Snake> opair in snakes) {
if (opair.Key == "bot")
continue;
current.touchHandler(opair.Value);
}
// Handle snake eating food
current.foodHandler(food);
// Display own Snake
setSnakePixels(canvas, current, colors[kvp.Key]);
}
// Display shared Food
Color red = new(255, 0, 0);
canvas.SetPixel(food.x*2, food.y*2, red);
canvas.SetPixel(food.x*2+1, food.y*2, red);
canvas.SetPixel(food.x*2, food.y*2+1, red);
canvas.SetPixel(food.x*2+1, food.y*2+1, red);
setPixelsOf(canvas, food, red);
// Swap Frame on Vsync
canvas = matrix.SwapOnVsync(canvas);
// Display Debug / Score
Console.ForegroundColor = ConsoleColor.White;
Console.SetCursorPosition(0, 0);
Console.WriteLine($"Score: {self.length-self.baseLen}");
// Save previous snakes
if (isMultiplayer) {
snakes = new();
if (isReady)
Connection.Send(Connection.self, $"PlayerUpdate {self.ToString()}");
}
// Fix framerate
frameCount = (frameCount+1)%1000000;
frameCount = (frameCount + 1) % 1000000;
int elapsed = (int)stopwatch.ElapsedMilliseconds;
if (elapsed < frameMills)
Thread.Sleep(frameMills - elapsed);
}
}
private static bool multiplayerConnect() {
// Initialize connection parameters
Connection.Initialize();
Connection.messageEvents.Add(new("PlayerUpdate", playerUpdate));
Connection.messageEvents.Add(new("FoodUpdate", foodUpdate));
Connection.messageEvents.Add(new("Created", onGetCode));
Connection.messageEvents.Add(new("Joined", onGetFood));
Connection.messageEvents.Add(new("ShowLobbies", showLobbies));
// Ask for user input for IP address and port number
Console.WriteLine("Angabe der IP Adresse und Portnummer des anderen Servers");
Console.Write("( Format 'IPv4:Port' ): ");
string ipIn = Console.ReadLine();
if (ipIn.ToLower() == "exit")
return false;
Connection.ClientConnect(ipIn);
// Handle lobby-connect
while (true) {
Console.Clear();
Console.WriteLine("################################");
Console.WriteLine("Beitreten einer Lobby:");
Console.WriteLine("0) Zurück");
Console.WriteLine("1) Lobby erstellen");
Console.WriteLine("2) Lobby beitreten");
Console.WriteLine("################################");
Console.WriteLine();
Console.Write("Wahl (0-2): ");
string input = Console.ReadLine();
// Handle user input with switch statement
bool ready = true;
switch (input) {
default:
// Invalid input was given
Console.Write("Ungültige Eingabe oder Fehler. Bitte erneut versuchen. ");
Console.ReadKey(true);
ready = false;
break;
case "0":
// Return to previous section
Connection.self.Close();
return false;
case "1":
// Open new Lobby on connected Server
string msg = $"NewLobby {food.ToString()}";
Connection.Send(Connection.self, msg);
break;
case "2":
// List opened lobbies and such
ready = multiplayerLobbyList();
break;
}
// Only break out of while-Loop on successful selection
if (ready)
break;
}
// Wait for server-side response on lobby creation/join
while (lobbyCode == "")
Thread.Sleep(500);
// Exit section in multiplayer mode
isMultiplayer = true;
return true;
}
private static bool multiplayerLobbyList() {
// Ask server for open lobbies
Connection.Send(Connection.self, "ListLobbies");
// Wait until lobby list is received
while (lobbyCode == "")
Thread.Sleep(500);
// Interpret the lobby list
string[] listed = lobbyCode.Split('-');
lobbyCode = "";
// Start section for lobby selection
int current = 0;
while (true) {
Console.Clear();
Console.WriteLine("################################");
Console.WriteLine("Beitreten einer Lobby:");
Console.WriteLine("0) Zurück");
// Loop through available lobbies from #<current> and up to 5 lobbies ahead
for (int i = 1; i < 5; i++)
if (current+i < listed.Length+1) {
string[] next = listed[current+i-1].Split(',');
Console.WriteLine($"{i}) Code: {next[0]} - Spieler: {next[1]}");
}
// Only display option when there are more available
if (listed.Length > current+5)
Console.WriteLine("6) Weiter");
Console.WriteLine("################################");
Console.WriteLine();
Console.Write("Wahl (0-2): ");
string input = Console.ReadLine();
// Handle user input with switch statement
bool ready = true;
switch (input) {
default:
// Check if input corresponds to a lobby, else an invalid input was given
int parse = int.TryParse(input, out parse) ? parse : 0;
if (parse <= 0) {
// Output invalid input message
Console.Write("Ungültige Eingabe oder Fehler. Bitte erneut versuchen. ");
Console.ReadKey(true);
ready = false;
// Select lobby from user input
} else
lobbyCode = listed[current+parse-1].Split(',')[0];
break;
case "0":
// If not at the beginning of the list, go back in the list
if (current != 0) {
ready = false;
current -= 5;
break;
}
// Otherwise exit section
return false;
case "6":
// Go forward in the list if not already at the end
ready = false;
if (listed.Length > current+5)
current += 5;
else {
// Output invalid input message
Console.Write("Ungültige Eingabe oder Fehler. Bitte erneut versuchen. ");
Console.ReadKey(true);
}
break;
}
// Only break out of while-Loop on successful selection
if (ready)
break;
}
// Join the selected lobby, continue in multiplayer mode
Connection.Send(Connection.self, $"JoinLobby {lobbyCode}");
return true;
}
// Method to display any given snake on any given canvas with any given color as 2x2 pixels
private static void setSnakePixels(RGBLedCanvas canv, Snake other, Color col) {
canv.SetPixel(self.ownPos.x*2, self.ownPos.y*2, col);
canv.SetPixel(self.ownPos.x*2+1, self.ownPos.y*2, col);
canv.SetPixel(self.ownPos.x*2, self.ownPos.y*2+1, col);
canv.SetPixel(self.ownPos.x*2+1, self.ownPos.y*2+1, col);
foreach (Segment s in self.tailPos) {
canv.SetPixel(s.x*2, s.y*2, col);
canv.SetPixel(s.x*2+1, s.y*2, col);
canv.SetPixel(s.x*2, s.y*2+1, col);
canv.SetPixel(s.x*2+1, s.y*2+1, col);
setPixelsOf(canv, other.ownPos, col);
foreach (Segment s in other.tailPos)
setPixelsOf(canv, s, col);
}
// Set Pixel at given coordinates
private static void setPixelsOf(RGBLedCanvas canv, Segment seg, Color col) {
for (int i = 0; i < sw; i++)
for (int j = 0; j < sh; j++)
canv.SetPixel(seg.x*sw+i , seg.y*sh+j , col);
}
// Method to choose what a given index has to do
public static void enterSelection() {
switch (selectionIndex) {
case 1:
selection = false;
break;
case 0:
selection = false;
frameCount = 0;
break;
}
}
// Async Callback to receive the list of open lobbies
private static void showLobbies(Socket other, string rest) {
lobbyCode = rest;
// Method to End the Game and return to Menu
public static void EndGame() {
selection = false;
gameOver = true;
frameCount = 0;
keypressFrame = 0;
}
// Async Callback to receive an update from another player
private static void playerUpdate(Socket other, string rest) {
Snake parsedSnake = Snake.Parse(rest);
if (parsedSnake != null)
snakes.Add(parsedSnake);
// Method to Steer Snake upwards
public static void SteerUp(Snake snake) {
if (snake.my != 1) {
snake.my = -1;
snake.mx = 0;
}
}
// Async Callback to receive an update for food
private static void foodUpdate(Socket other, string rest) {
try {
string[] splits = rest.Split(',');
food = new(int.Parse(splits[0]), int.Parse(splits[1]));
} catch (Exception e) { }
// Method to Steer Snake downwards
public static void SteerDown(Snake snake) {
if (snake.my != -1) {
snake.my = 1;
snake.mx = 0;
}
}
// Async Callback to receive the lobby code from the server
private static void onGetCode(Socket other, string code) {
lobbyCode = code;
// Method to Steer Snake to the left
public static void SteerLeft(Snake snake) {
if (snake.mx != 1) {
snake.mx = -1;
snake.my = 0;
}
}
// Async Callback to receive the lobby code and current food position in lobby
private static void onGetFood(Socket other, string rest) {
string[] splits = rest.Split('-');
lobbyCode = splits[0];
food = Segment.Parse(splits[1]);
}
// Method to reset any given snake to its base parameters
private static void resetSelf(Snake other) {
// Replace any characters at score
int scoreLen = $"Score: {other.length-other.baseLen}".Length;
Console.SetCursorPosition(0, 0);
for (int i = 0; i < scoreLen; i++)
Console.Write(' ');
// Reset lengt of Snake
other.length = other.baseLen;
}
// Method to parse a serialized list of 'Segments' into a List<Segment> Object
private static List<Segment> parseSegments(string _segments) {
List<Segment> newSegments = new();
string[] splits = _segments.Split('-');
foreach (string s in splits)
newSegments.Add(Segment.Parse(s));
return newSegments;
// Method to Steer Snake to the right
public static void SteerRight(Snake snake) {
if (snake.mx != -1) {
snake.mx = 1;
snake.my = 0;
}
}
// Class to represent an instance of a Snake
public class Snake {
public Segment ownPos, lastPos;
public List<Segment> tailPos = new();
public int length = 0, baseLen = 0;
public int mx = 1, my = 0;
public Segment ownPos, lastPos;
public int length;
public int mx = 1, my;
// Constructor for a simple Snake with x and y coordinates
public Snake(int _x, int _y, int _len=5) {
baseLen = _len;
length = _len;
public Snake(int _x, int _y) {
length = baseLength;
ownPos = new(_x, _y);
// Prepare Tail based on length, position and base movement
tailPos.Add(new(_x-mx, _y));
for (int i = 0; i < _len-1; i++) {
for (int i = 0; i < length-1; i++) {
Segment previous = tailPos[0];
Segment next = new(previous.x-mx, previous.y);
tailPos.Insert(0, next);
@ -412,7 +258,7 @@ namespace SnakeGame {
}
// Constructor to copy a snake from given parameters (to avoid object reference from memory)
public Snake(Segment _ownPos, Segment _lastPos, List<Segment> _tailPos, int _mx, int _my, int _length, int _baseLen) {
public Snake(Segment _ownPos, Segment _lastPos, List<Segment> _tailPos, int _mx, int _my, int _length) {
ownPos = _ownPos.Copy();
lastPos = _lastPos.Copy();
foreach (Segment t in _tailPos)
@ -420,36 +266,6 @@ namespace SnakeGame {
mx = _mx;
my = _my;
length = _length;
baseLen = _baseLen;
}
// Method to interpret a string as a serialized snake
public static Snake Parse(string snake) {
Snake parsedSnake = null;
// Scratch process if any inconsistencies in the format cause trouble
try {
string[] attributes = snake.Split(';');
parsedSnake = new(
Segment.Parse(attributes[0]),
Segment.Parse(attributes[1]),
parseSegments(attributes[2]),
int.Parse(attributes[3]),
int.Parse(attributes[4]),
int.Parse(attributes[5]),
int.Parse(attributes[6])
);
}
catch (Exception) { }
return parsedSnake;
}
// Serialize this Snake Object into a string
public override string ToString() {
string segStr = "";
for (int i = 0; i < tailPos.Count; i++)
segStr += tailPos[i].ToString() + ((i < tailPos.Count-1) ? "-" : "");
return $"{ownPos.ToString()};{lastPos.ToString()};{segStr};{mx};{my};{length};{baseLen}";
}
// Check if any given x and y coordinates are part of this Snake Object (including Tail/Segments)
@ -467,13 +283,13 @@ namespace SnakeGame {
bool xC = ownPos.x == other.ownPos.x;
bool yC = ownPos.y == other.ownPos.y;
if (xC && yC)
resetSelf(this);
length = baseLength;
}
// Check for tail collision
foreach (Segment pos in other.tailPos)
if (pos.x == ownPos.x && pos.y == ownPos.y) {
resetSelf(this);
length = baseLength;
break;
}
}
@ -483,39 +299,6 @@ namespace SnakeGame {
if (ownPos.x == _food.x && ownPos.y == _food.y) {
length++;
food.setRandom(width, height);
if (isMultiplayer && this == self)
Connection.Send(Connection.self, $"FoodUpdate {food.ToString()}");
}
}
// Method to handle a keypress for this snake
public void keyHandler() {
// If key is pressed, handle logic
if (Console.KeyAvailable) {
switch (Console.ReadKey(true).Key) {
case ConsoleKey.Escape:
// Exit program.
Environment.Exit(0);
break;
case ConsoleKey.UpArrow:
// Only recognise keypress if not currently heading down
if (my != 1) { my = -1; mx = 0; }
break;
case ConsoleKey.DownArrow:
// Only recognise keypress if not currently heading up
if (my != -1) { my = 1; mx = 0; }
break;
case ConsoleKey.LeftArrow:
// Only recognise keypress if not currently heading right
if (mx != 1) { mx = -1; my = 0; }
break;
case ConsoleKey.RightArrow:
// Only recognise keypress if not currently heading left
if (mx != -1) { mx = 1; my = 0; }
break;
}
// Save keypress to detect player activity
keypressFrame = frameCount;
}
}
@ -549,19 +332,6 @@ namespace SnakeGame {
setRandom(width, height);
}
// Try parsing any given string into a Segment object. Return 'new Segment(0, 0)' as default.
public static Segment Parse(string segment) {
string[] splits = segment.Split(',');
int _x = int.TryParse(splits[0], out _x) ? _x : 0;
int _y = int.TryParse(splits[1], out _y) ? _y : 0;
return new(_x, _y);
}
// Serialize Segment object into a string
public override string ToString() {
return $"{x},{y}";
}
// Set own x and y coordinates randomly according to give width and height boundaries
public void setRandom(int _width, int _height) {
Random rng = new();

View File

@ -1,12 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Reference to local copy of https://gitlab1.ptb.de/waltem01/csharpsocketserver -->
<ItemGroup>
<ProjectReference Include="..\SocketTest\Net.Sockets\SocketLib.csproj" />
<Reference Include="RGBLedMatrix.dll" />
</ItemGroup>
<PropertyGroup>
<MainEntryPoint>SnakeGame.SnakeGame</MainEntryPoint>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>