Compare commits

...

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

11 changed files with 369 additions and 4384 deletions

5
.gitignore vendored
View File

@ -1,2 +1,3 @@
server/node_modules/*
gitUpdate.sh
.idea/*
bin/*
obj/*

BIN
RGBLedMatrix.dll Normal file

Binary file not shown.

348
SnakeGame.cs Normal file
View File

@ -0,0 +1,348 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System;
using rpi_rgb_led_matrix_sharp;
namespace SnakeGame {
public static class SnakeGame {
public static int baseLength = 5;
private static Segment food = new();
private static RGBLedMatrix? _matrix;
private static RGBLedCanvas? _canvas;
public static bool gameOver, selection;
private static int FPS = 10, timeout = 10;
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)}};
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)));
}
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();
// Initialize RGBLedMatrix
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();
// Clear canvas
canvas.Clear();
// If player is absent, automatically steer sneak towards food (very simple)
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;
}
// 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);
setPixelsOf(canvas, food, red);
// Swap Frame on Vsync
canvas = matrix.SwapOnVsync(canvas);
// Fix framerate
frameCount = (frameCount + 1) % 1000000;
int elapsed = (int)stopwatch.ElapsedMilliseconds;
if (elapsed < frameMills)
Thread.Sleep(frameMills - elapsed);
}
}
// 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) {
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;
}
}
// Method to End the Game and return to Menu
public static void EndGame() {
selection = false;
gameOver = true;
frameCount = 0;
keypressFrame = 0;
}
// Method to Steer Snake upwards
public static void SteerUp(Snake snake) {
if (snake.my != 1) {
snake.my = -1;
snake.mx = 0;
}
}
// Method to Steer Snake downwards
public static void SteerDown(Snake snake) {
if (snake.my != -1) {
snake.my = 1;
snake.mx = 0;
}
}
// Method to Steer Snake to the left
public static void SteerLeft(Snake snake) {
if (snake.mx != 1) {
snake.mx = -1;
snake.my = 0;
}
}
// 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 List<Segment> tailPos = new();
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) {
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 < length-1; i++) {
Segment previous = tailPos[0];
Segment next = new(previous.x-mx, previous.y);
tailPos.Insert(0, next);
}
// Prepare lastPos as non-null
Segment lastNew = tailPos[0];
lastPos = new(lastNew.x-mx, lastNew.y);
// Set random heading
switch ((new Random()).Next(3)) {
case 0:
mx = 1; my = 0;
break;
case 1:
mx = 0; my = 1;
break;
case 2:
mx = 0; my = -1;
break;
}
}
// 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) {
ownPos = _ownPos.Copy();
lastPos = _lastPos.Copy();
foreach (Segment t in _tailPos)
tailPos.Add(t.Copy());
mx = _mx;
my = _my;
length = _length;
}
// Check if any given x and y coordinates are part of this Snake Object (including Tail/Segments)
public bool IsPartOfSelf(int _x, int _y) {
foreach (Segment s in tailPos)
if (s.x == _x && s.y == _y)
return true;
return ownPos.x == _x && ownPos.y == _y;
}
// Method to handle collision of this snake with any other given snake
public void touchHandler(Snake other) {
// If is not self, check for head collision
if (!other.Equals(this)) {
bool xC = ownPos.x == other.ownPos.x;
bool yC = ownPos.y == other.ownPos.y;
if (xC && yC)
length = baseLength;
}
// Check for tail collision
foreach (Segment pos in other.tailPos)
if (pos.x == ownPos.x && pos.y == ownPos.y) {
length = baseLength;
break;
}
}
// Method to handle collision with a Food (Segment) Object, 'consume' it if possible
public void foodHandler(Segment _food) {
if (ownPos.x == _food.x && ownPos.y == _food.y) {
length++;
food.setRandom(width, height);
}
}
// Method to calculate movement of this Snake
public void move() {
// Move snake according to last input and add prev. pos. to tail
tailPos.Add(ownPos.Copy());
ownPos.x = (ownPos.x + mx + width) % width;
ownPos.y = (ownPos.y + my + height) % height;
// Remove any tail above current length
while (tailPos.Count > length) {
lastPos = tailPos[0];
tailPos.RemoveAt(0);
}
}
}
// Base class for logic and positions, i.e. a Segment of a Snake or Food
public class Segment {
public int x, y;
// Constructor to save given x and y coordinates
public Segment(int _x, int _y) {
x = _x;
y = _y;
}
// Constructor to set own x and y to random coordinates constrained by global width and height
public Segment() {
setRandom(width, height);
}
// Set own x and y coordinates randomly according to give width and height boundaries
public void setRandom(int _width, int _height) {
Random rng = new();
x = rng.Next(_width);
y = rng.Next(_height);
}
// Create copy of this instance of a Segment Object
public Segment Copy() {
return new(x, y);
}
}
}
}

18
SnakeGame.csproj Normal file
View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Reference Include="RGBLedMatrix.dll" />
</ItemGroup>
<PropertyGroup>
<MainEntryPoint>SnakeGame.SnakeGame</MainEntryPoint>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishReadyToRun>true</PublishReadyToRun>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
</PropertyGroup>
</Project>

View File

@ -1,9 +0,0 @@
<html>
<head>
</head>
<body onload="loaded = true">
<script src="/socket.io/socket.io.js"></script>
<script src="js/client.js"></script>
<script src="js/canvas.js"></script>
</body>
</html>

View File

@ -1,216 +0,0 @@
var canv = document.createElement("canvas");
const ww = window.innerWidth;
const wh = window.innerHeight;
const rc = ww > wh;
const cw = (rc) ? ww : wh;
const ch = (rc) ? wh : ww;
canv.width = ww;
canv.height = wh;
canv.style.position = "absolute";
canv.style.top = "0px";
canv.style.left = "0px";
var ctx = canv.getContext("2d");
document.body.appendChild(canv);
var mouse = {x: 0, y: 0};
document.addEventListener("mousemove", evt => {
var rect = canv.getBoundingClientRect();
mouse = {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
});
document.addEventListener("mousedown", () => {
const mx = (rc) ? mouse.x : mouse.y;
const my = (rc) ? mouse.y : mouse.x;
const LEFT = mx < cw * (1/3);
const RIGHT = mx > cw * (2/3);
const BOTTOM = my > ch * (2/3);
const TOP = my < ch * (1/3);
const copy = self.b;
if (LEFT && !BOTTOM && !TOP) {
//Left
addKeyCode(37);
} else if (RIGHT && !BOTTOM && !TOP) {
//Right
addKeyCode(39);
} else if (BOTTOM && !RIGHT && !LEFT) {
//Down
addKeyCode(38);
} else if (TOP && !RIGHT && !LEFT) {
//Up
addKeyCode(40);
}
});
document.addEventListener("keydown",evt => {
addKeyCode(evt.keyCode);
});
function addKeyCode(kC) {
let isInB = false, isOpposite = false;
for (let i = 0; i < self.b.length; i++) {
const e = self.b[i];
if (e == kC) {
isInB = true;
break;
}
/*
Didn't get this to work, lol.
Instead just checked for opposite on line 54, 60, 66, 72.
if (e == kC-2 || e == kC+2) {
isOpposite = true;
break;
}*/
}
if (!isOpposite && !isInB) {
self.b.push(kC);
}
}
class Snake {
constructor(px, py) {
this.x = px;
this.y = py;
this.h = {x: 0, y: 0};
this.l = 5;
this.t = Array();
this.b = [Math.floor(Math.random()*4+37)];
this.p = {x: 0, y: 0};
}
// Didn't want each socket.emit() to also sent update and show as method from Snake, so seperate functions it is.
}
const cells = 40, cwidth = ch/cells, fCL = 6;
let self = new Snake(Math.floor(cells/2), Math.floor(cells/2)), fC = 0;
for (let i = 0; i < self.l; i++) {
update(self, true);
}
function update(snake, ignore = false) {
if (fC%fCL == 0) {
const e = snake.b.splice(0,1)[0];
switch (e) {
case 37:
if (snake.h.x != 1) {
snake.h.x = -1;
snake.h.y = 0;
}
break;
case 38:
if (snake.h.y != 1) {
snake.h.x = 0;
snake.h.y = -1;
}
break;
case 39:
if (snake.h.x != -1) {
snake.h.x = 1;
snake.h.y = 0;
}
break;
case 40:
if (snake.h.y != -1) {
snake.h.x = 0;
snake.h.y = 1;
}
break;
}
snake.t.push({x: snake.x, y: snake.y});
while (snake.t.length > snake.l) {
snake.p = snake.t.splice(0,1);
//snake.t.shift();
}
snake.x = (snake.x + snake.h.x + cells) % cells;
snake.y = (snake.y + snake.h.y + cells) % cells;
if (snake.x == food.x && snake.y == food.y) {
snake.l++;
socket.emit('snakeFoodUpdate');
}
for (let i = 0; i < snake.t.length; i++) {
if (snake.t[i].x == snake.x && snake.t[i].y == snake.y) {
snake.l = 5;
break;
}
}
for (let i = 0; i < players.length; i++) {
if (players[i].x == snake.x && players[i].y == snake.y) {
snake.l = 5;
break;
}
let broke = false;
for (let j = 0; j < players[i].t.length; j++) {
if (players[i].t[j].x == snake.x && players[i].t[j].y == snake.y) {
snake.l = 5;
broke = true;
break;
}
}
if (broke) {
break;
}
}
if (!ignore) {
socket.emit('snakePlayerUpdate', {ownID: selfID, data: snake});
}
}
}
function show(snake) {
if (snake == self) {
ctx.fillStyle = "green";
ctx.strokeStyle = "green";
} else {
ctx.fillStyle = "blue";
ctx.strokeStyle = "blue";
}
const sx = (rc) ? snake.x : snake.y;
const sy = (rc) ? snake.y : snake.x;
ctx.fillRect(sx*cwidth+1, sy*cwidth+1, cwidth-2, cwidth-2);
if (snake == self) {
ctx.fillStyle = "lime";
ctx.strokeStyle = "lime";
} else {
ctx.fillStyle = "cyan";
ctx.strokeStyle = "cyan";
}
snake.t.forEach(e => {
const ex = (rc) ? e.x : e.y;
const ey = (rc) ? e.y : e.x;
ctx.fillRect(ex*cwidth+1, ey*cwidth+1, cwidth-2, cwidth-2);
});
ctx.fillStyle = "red";
ctx.strokeStyle = "red";
const fx = (rc) ? food.x : food.y;
const fy = (rc) ? food.y : food.x;
ctx.fillRect(fx*cwidth+1, fy*cwidth+1, cwidth-2, cwidth-2);
}
var frames = 60, interv = setInterval(() => {
ctx.fillStyle = "grey";
ctx.fillRect(0,0,canv.width,canv.height);
if (selfID != -1 && loaded) {
var t = ctx.getTransform();
const nwidth = cw/2-ch/2;
if (rc) {
ctx.translate(nwidth, 0);
} else {
ctx.translate(0, nwidth);
}
ctx.fillStyle = "black";
ctx.fillRect(0, 0, ch, ch);
update(self);
show(self);
players.forEach(show);
ctx.setTransform(t);
fC = (fC+1001)%1000;
}
}, 1000/frames);

View File

@ -1,18 +0,0 @@
const socket = io();
let selfID = -1, players = Array(), loaded = false, food = {x: -1, y: -1};
socket.on('connected', d => {
//console.log("Connected!");
players = d.pdata;
selfID = d.ownID;
food = d.food;
});
socket.on('snakeUpdatePlayers', p => {
//console.log("Recieved "+ p.pdata.length +" players.");
players = Array();
p.pdata.forEach(e => {
players.push(e);
});
food = p.food;
});

BIN
librgbmatrix.so Normal file

Binary file not shown.

4019
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.24.0",
"cheerio": "^1.0.0-rc.10",
"express": "^4.17.3",
"global-agent": "^3.0.0",
"nodemon": "^2.0.15",
"reddit-image-fetcher": "^2.0.10",
"reddit.images": "^1.0.7",
"socket.io": "^4.4.1"
}
}

View File

@ -1,98 +0,0 @@
const port = 3000;
const http = require('http');
const express = require('express');
const socketio = require('socket.io');
const { exec } = require('child_process');
const app = express();
const clientPath = __dirname+'/../client';
console.log('Serving static from ' + clientPath);
app.use(express.static(clientPath));
const server = http.createServer(app);
const io = socketio(server);
app.post('/receiveUpdate', (req, res) => {
res.status(200).json({success:true});
exec(`sh ${__dirname}/../gitUpdate.sh`, (error, stdout, stderr) => {
console.log(stdout);
console.error(stderr);
if (error !== null) {
console.error(`exec error: ${error}`);
}
});
});
server.on('error', err => {
console.error('Server error:', err);
});
server.listen(port, () => {
console.log('Server Started on Port '+port);
});
const cells = 40;
let snakePlayers = Array(), snakeFood = {x: Math.floor(Math.random()*cells), y: Math.floor(Math.random()*cells)};
io.on('connection', socket => {
let id = -1;
console.log("New Player has connected.");
while (true) {
let current = Math.floor(Math.random()*89+10);
let isUsed = false;
for (let i = 0; i < snakePlayers.length; i++) {
if (snakePlayers[i].uid == current) {
isUsed = true;
break;
}
}
if (!isUsed) {
id = current;
break;
}
}
if (id != -1) {
console.log('Handing ID', id);
let temp = Array();
snakePlayers.forEach(p => {
if (p.data != null) {
temp.push(p.data);
}
});
socket.emit('connected', {pdata: temp, ownID: id, food: snakeFood});
snakePlayers.push({socket, ownID: id, data: null});
}
socket.on('disconnect', () => {
for (let i = 0; i < snakePlayers.length; i++) {
if (snakePlayers[i].socket == socket) {
console.log("Disconnecting SNAKEGAME User. ID", snakePlayers[i].ownID);
snakePlayers.splice(i, 1);
break;
}
}
});
socket.on('snakeFoodUpdate', () => {
snakeFood = {x: Math.floor(Math.random()*cells), y: Math.floor(Math.random()*cells)};
});
socket.on('snakePlayerUpdate', p => {
for (let i = 0; i < snakePlayers.length; i++) {
if (p.ownID == snakePlayers[i].ownID) {
snakePlayers[i].data = p.data;
let temp = Array();
snakePlayers.forEach(d => {
if (d.ownID != p.ownID && d.data != null) {
temp.push(d.data);
}
});
snakePlayers[i].socket.emit('snakeUpdatePlayers', {pdata: temp, food: snakeFood});
break;
}
}
});
});