using System; using System.IO; using System.Linq; using System.Threading; using System.Collections.Generic; using rpi_rgb_led_matrix_sharp; namespace RPI_Matrix { public static class RPI_Matrix { // Initialize variables private static int debugIndex; private static bool debugMenu, debugSelect; private static List gamepadListeners = new(); private static int selectionIndex, keypressFrame, frameCount; private static bool hasSelected, isInGame, isTetris, isFlappy, isSnake; private static bool selection = true, isFocused, focusHeight, focusGravity, focusJump, focusLength; public static void Main(string[] args) { // Connect to RGB LED Matrix RGBLedMatrix matrix = new( new RGBLedMatrixOptions { GpioSlowdown = 5, ChainLength = 3, Parallel = 3, Rows = 64, Cols = 64, }); RGBLedCanvas canvas = matrix.CreateOffscreenCanvas(); // Initialize Games SnakeGame.SnakeGame.Init(matrix, canvas); FlappyBird.FlappyBird.Init(matrix, canvas); Tetris.Tetris.Init(matrix, canvas); // Start listening and interpreting Gamepad input in parallel new Thread(InterpretGamepadInput).Start(); // Initialize Text-Font and -Color RGBLedFont font = new("fonts/9x18B.bdf"); Color col = new(255, 255, 255); // Game Menu while (true) { // Restart loop Console.Clear(); selection = true; // Game selection Menu while (selection) { // Display Games and Cursor on Matrix canvas.Clear(); canvas.DrawText(font, 0, 18, col, "Spielauswahl:"); canvas.DrawText(font, 0, 54, col, " ) SnakeGame"); canvas.DrawText(font, 0, 72, col, " ) FlappyBird"); canvas.DrawText(font, 0, 90, col, " ) Tetris"); canvas.DrawText(font, 0, 126, col, " ) Einstellungen"); canvas.DrawText(font, 0, 144, col, " ) Beenden"); // Cursor is a '#' at (0, 24 + i * 8) where i may be [0, 1, 2, 3, 5] int nx = selectionIndex + ((selectionIndex >= 3) ? 1 : 0); canvas.DrawText(font, 0, 54 + nx * 18, col, "#"); canvas = matrix.SwapOnVsync(canvas); // Handle key presses for selecting a game if (Console.KeyAvailable) { keypressFrame = frameCount; switch (Console.ReadKey(true).Key) { case ConsoleKey.Escape: return; case ConsoleKey.UpArrow: if (!isInGame && selectionIndex > 0) selectionIndex--; break; case ConsoleKey.DownArrow: if (!isInGame && selectionIndex < 4) selectionIndex++; break; case ConsoleKey.Enter: if (!isInGame) hasSelected = true; break; } } // Use selected index to start a Game if (hasSelected) { hasSelected = false; switch (selectionIndex) { case 0: // Start SnakeGame in Singleplayer playSnakeGame(); break; case 1: // Start FlappyBird playFlappyBird(); break; case 2: // Start Tetris playTetris(); break; case 3: // Open debug Menu openDebugMenu(matrix, canvas); break; case 4: // Exit program Environment.Exit(0); break; } } // Reset Game identification variables after exiting isInGame = false; isTetris = false; isFlappy = false; isSnake = false; // If player is absent for 15 seconds, start SnakeGame in Bot Mode if (frameCount >= keypressFrame + 150) { keypressFrame = frameCount; playSnakeGame("-b"); } // Wait for next frame and keep count Thread.Sleep(100); frameCount = (frameCount+1)%1000000; } } } private static void openDebugMenu(RGBLedMatrix matrix, RGBLedCanvas canvas) { // Set identification variables and load settings isInGame = true; selection = false; // Initialize Text-Font and -Color RGBLedFont font = new("fonts/9x18B.bdf"); Color col = new(255, 255, 255); // Game selection Menu debugMenu = true; while (debugMenu) { // Display Games and Cursor on Matrix canvas.Clear(); canvas.DrawText(font, 0, 18, col, "Tetris:"); canvas.DrawText(font, 0, 36, col, $" ) PTB Logo {(Tetris.Tetris.ptbLogo ? "I" : "O")}"); canvas.DrawText(font, 0, 54, col, "FlappyBird:"); canvas.DrawText(font, 0, 72, col, $" ) Höhe {FlappyBird.FlappyBird.spaceHeight}"); canvas.DrawText(font, 0, 90, col, $" ) Gravitation {FlappyBird.FlappyBird.gravity}"); canvas.DrawText(font, 0, 108, col, $" ) Sprung Ges. {FlappyBird.FlappyBird.jumpVel}"); canvas.DrawText(font, 0, 126, col, "SnakeGame"); canvas.DrawText(font, 0, 144, col, $" ) Basis Länge {SnakeGame.SnakeGame.baseLength}"); canvas.DrawText(font, 0, 180, col, " ) Zurück"); // Cursor is a '#' at (0, 24 + i * 8) where i may be [0, 2] int nx = debugIndex; if (debugIndex >= 1) nx++; if (debugIndex >= 4) nx++; if (debugIndex >= 5) nx++; canvas.DrawText(font, 0, 36 + nx * 18, col, "#"); canvas = matrix.SwapOnVsync(canvas); // Handle key presses for selecting a game if (Console.KeyAvailable) { switch (Console.ReadKey(true).Key) { case ConsoleKey.Escape: return; case ConsoleKey.UpArrow: if (debugIndex > 0) debugIndex--; break; case ConsoleKey.DownArrow: if (debugIndex < 5) debugIndex++; break; case ConsoleKey.Enter: debugSelect = true; break; } } // Use selected index to change a setting if (debugSelect) { debugSelect = false; switch (debugIndex) { case 0: // Toggle Tetris PTB Logo Tetris.Tetris.ptbLogo = !Tetris.Tetris.ptbLogo; break; case 1: // Focus Height to be changed with arrow keys focusHeight = true; isFocused = true; break; case 2: // Focus Gravity to be changed with arrow keys focusGravity = true; isFocused = true; break; case 3: // Focus Jump to be changed with arrow keys focusJump = true; isFocused = true; break; case 4: // Go back to menu focusLength = true; isFocused = true; break; case 5: // Go back to menu debugMenu = false; break; } } // Wait for next frame and keep count if (debugMenu) Thread.Sleep(100); } } private static void playSnakeGame(string arg = "") { // Set Game identification variables and start playing SnakeGame isSnake = true; isInGame = true; selection = false; SnakeGame.SnakeGame.players = gamepadListeners; SnakeGame.SnakeGame.Main(new[] { arg }); } private static void playFlappyBird() { // Set Game identification variables and start playing FlappyBird isFlappy = true; isInGame = true; selection = false; FlappyBird.FlappyBird.Main(); } private static void playTetris() { // Set Game identification variables and start playing Tetris isTetris = true; isInGame = true; selection = false; Tetris.Tetris.Main(); } private static void InterpretGamepadInput() { // Read file "/dev/input/js*" in Raspbian for Raspberry 4 4GB. // All inputs on a Gamepad are accessible from within here. // Interpreting this data may only work in this program using a SNES Controller. while (true) { string dir = "/dev/input/"; IEnumerable files = from retrieved in Directory.EnumerateFiles(dir) where retrieved.Contains("js") select retrieved; foreach (string f in files) { string name = f.Replace(dir, ""); if (!gamepadListeners.Contains(name)) { gamepadListeners.Add(name); new Thread(() => { try { WatchInputFile(dir, f); } catch (Exception) { gamepadListeners.Remove(name); } }).Start(); } } Thread.Sleep(500); } } private static void WatchInputFile(string dir, string file) { FileInfo targetFile = new FileInfo(file); using FileStream fs = targetFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // Initialize Buffer (byte Array) to put read bytes into byte[] buffer = new byte[1024]; while (fs.Read(buffer) > 0) { // If inputs were registered, save frame # keypressFrame = frameCount; // Do the same in Tetris if it's in the 'GameOver' Screen if (isTetris && Tetris.Tetris.selection) Tetris.Tetris.frameCount = 0; // And do it again in SnakeGame while it's playing. else if (isSnake && !SnakeGame.SnakeGame.gameOver) SnakeGame.SnakeGame.keypressFrame = SnakeGame.SnakeGame.frameCount; // Convert Buffer to Hexadecimal, each Byte separated by a '-' Delegate method = BitConverter.ToString(buffer, 4, 4) switch { "01-00-01-00" => XButtonPressed, "01-00-01-01" => AButtonPressed, "01-00-01-02" => BButtonPressed, "01-00-01-04" => LButtonPressed, "01-00-01-05" => RButtonPressed, "01-00-01-09" => StartButtonPressed, "01-80-02-01" => UpArrowButtonPressed, "FF-7F-02-01" => DownArrowPressed, "01-80-02-00" => LeftArrowPressed, "FF-7F-02-00" => RightArrowPressed, _ => (string player) => {} }; // Execute function mapped by buffer/button press as current player method.DynamicInvoke(file.Replace(dir, "")); } } private static void RightArrowPressed(string player) { // Code for Arrow-Right Press // If in SnakeGame, Steer the Snake to the right if (isSnake) SnakeGame.SnakeGame.SteerRight(SnakeGame.SnakeGame.snakes[player]); // If in Tetris, move current Block to the right else if (isTetris) Tetris.Tetris.moveRightSide(); } private static void LeftArrowPressed(string player) { // Code for Arrow-Left Press // If in SnakeGame, Steer the Snake to the left if (isSnake) SnakeGame.SnakeGame.SteerLeft(SnakeGame.SnakeGame.snakes[player]); // If in Tetris, move current Block to the left else if (isTetris) Tetris.Tetris.moveLeftSide(); } private static void DownArrowPressed(string player) { // Code for Arrow-Down Press // If in the selection screen, decrement index if (!isInGame && selectionIndex < 4) selectionIndex++; // If in SnakeGame, Steer the Snake downwards else if (isSnake) { // Handle index in 'GameOver' Screen if (SnakeGame.SnakeGame.selection && SnakeGame.SnakeGame.selectionIndex < 1) SnakeGame.SnakeGame.selectionIndex++; // If in SnakeGame, Steer the Snake downwards else SnakeGame.SnakeGame.SteerDown(SnakeGame.SnakeGame.snakes[player]); // If in Tetris and in Tetris' 'GameOver' Screen, decrement its index. } else if (isTetris) // If in Tetris and in Tetris' 'GameOver' Screen, increment its index. if (Tetris.Tetris.selection && Tetris.Tetris.selectionIndex < 1) Tetris.Tetris.selectionIndex++; // Otherwise, move Tetris piece down else Tetris.Tetris.moveDownOnce(); else if (isFlappy && FlappyBird.FlappyBird.selection && FlappyBird.FlappyBird.selectionIndex < 1) FlappyBird.FlappyBird.selectionIndex++; else if (focusGravity && FlappyBird.FlappyBird.gravity > 0) FlappyBird.FlappyBird.gravity -= 0.01f; else if (focusHeight && FlappyBird.FlappyBird.spaceHeight > 2) FlappyBird.FlappyBird.spaceHeight -= 1; else if (focusJump && FlappyBird.FlappyBird.jumpVel > 0) FlappyBird.FlappyBird.jumpVel -= 0.01f; else if (focusLength && SnakeGame.SnakeGame.baseLength > 0) SnakeGame.SnakeGame.baseLength -= 1; else if (debugIndex < 5 && !isFocused) debugIndex++; } private static void UpArrowButtonPressed(string player) { // Code for Arrow-Up Press // If in the selection screen, decrement index if (!isInGame && selectionIndex > 0) selectionIndex--; else if (isSnake) { // Handle index in 'GameOver' Screen if (SnakeGame.SnakeGame.selection && SnakeGame.SnakeGame.selectionIndex > 0) SnakeGame.SnakeGame.selectionIndex--; // If in SnakeGame, Steer the Snake upwards else SnakeGame.SnakeGame.SteerUp(SnakeGame.SnakeGame.snakes[player]); // If in Tetris and in Tetris' 'GameOver' Screen, decrement its index. } else if (isTetris && Tetris.Tetris.selection && Tetris.Tetris.selectionIndex > 0) Tetris.Tetris.selectionIndex--; else if (isFlappy && FlappyBird.FlappyBird.selection && FlappyBird.FlappyBird.selectionIndex > 0) FlappyBird.FlappyBird.selectionIndex--; else if (focusGravity) FlappyBird.FlappyBird.gravity += 0.01f; else if (focusHeight && FlappyBird.FlappyBird.spaceHeight < 58) FlappyBird.FlappyBird.spaceHeight += 1; else if (focusJump) FlappyBird.FlappyBird.jumpVel += 0.01f; else if (focusLength) SnakeGame.SnakeGame.baseLength += 1; else if (debugIndex > 0 && !isFocused) debugIndex--; } private static void StartButtonPressed(string player) { // Code for Start-Button Press // If in SnakeGame, end the Game and return to menu if (isSnake) SnakeGame.SnakeGame.EndGame(); else if (isFlappy) { // If in FlappyBirds' 'ready' Screen, return to menu if (!FlappyBird.FlappyBird.isReady) FlappyBird.FlappyBird.stopPlaying = true; // Otherwise go back to 'ready' Screen by ending the Game else { FlappyBird.FlappyBird.EndGame(); FlappyBird.FlappyBird.selection = false; } // If in Tetris, end the Game and return to menu } else if (isTetris) Tetris.Tetris.EndGame(); } private static void RButtonPressed(string player) { // Code for R-Button Press // If in Tetris, rotate the current Block Clockwise if (isTetris) Tetris.Tetris.rotateClockwise(); } private static void LButtonPressed(string player) { // Code for L-Button Press // If in Tetris, rotate the current Block counter-Clockwise if (isTetris) Tetris.Tetris.rotateCounterClockwise(); } private static void BButtonPressed(string player) { // Code for B-Button Press if (isFlappy) { // Jump if (FlappyBird.FlappyBird.isReady) FlappyBird.FlappyBird.JumpOnce(); // Start Game else FlappyBird.FlappyBird.isReady = true; // Place the current Block in Tetris } else if (isTetris) Tetris.Tetris.placeDownBlock(); else if (!isSnake) { focusGravity = false; focusLength = false; focusHeight = false; focusJump = false; isFocused = false; } } private static void AButtonPressed(string player) { // Code for A-Button Press // If not already in a Game, now ready up to select one. if (!isInGame) hasSelected = true; // Select given option in Tetris' 'GameOver' Screen else if (isSnake && SnakeGame.SnakeGame.selection) SnakeGame.SnakeGame.enterSelection(); else if (isFlappy && FlappyBird.FlappyBird.selection) FlappyBird.FlappyBird.enterSelection(); else if (isTetris && Tetris.Tetris.selection) Tetris.Tetris.enterSelection(); else debugSelect = true; } private static void XButtonPressed(string player) { // Code for X-Button Press if (isTetris) Tetris.Tetris.swapMemory(); } } }