I just started learning programming couple months ago and decided to do a console snake game. Everything works great expect for one thing.
If my snake is going upwards and I press down arrow and keep it pressed, my snake just stops and stays stopped even awhile after I stop pressing the button.
Same thing happens if my snake is going right and I press right arrow too long, I lose control for some time(but snake does not stop). It happens for every way(left,right,up,down).
I tried to compare the cki to another ConsoleKeyInfo with slight delay between them, but it doesn't matter. If I keep the key pressed, my program just stays that spot and updates for a key. (atleast I think thats the problem)
Is this a "feature" of the Console.ReadKey or is there some way arount this?
Keep in mind, that I just started so I don't know about the advanced stuff, yet.
Everything works flawless as long as I don't keep the key pressed more than 1 sec.
public void LiikutaMato() //movesnake
{
if (Console.KeyAvailable)
{
ConsoleKeyInfo cki;
cki = Console.ReadKey(true); // <-- I believe this is where it gets stuck
}
Play with this...it uses a tight loop to consume the keypresses until the set delay has expired. You also don't have to hold down the key to keep the snake moving. It was very responsive on my system:
class Program
{
public enum Direction
{
Up,
Down,
Right,
Left
}
static void Main(string[] args)
{
const int delay = 75;
string snake = "O";
char border = 'X';
int x, y;
int length;
bool crashed = false;
Direction curDirection = Direction.Up;
Dictionary<string, bool> eaten = new Dictionary<string, bool>();
Console.CursorVisible = false;
ConsoleKeyInfo cki;
bool quit = false;
while (!quit)
{
Console.Clear();
Console.Title = "Use 'a', 's', 'd' and 'w' to steer. Hit 'q' to quit.";
// draw border around the console:
string row = new String(border, Console.WindowWidth);
Console.SetCursorPosition(0, 0);
Console.Write(row);
Console.SetCursorPosition(0, Console.WindowHeight - 2);
Console.Write(row);
for (int borderY = 0; borderY < Console.WindowHeight - 2; borderY++)
{
Console.SetCursorPosition(0, borderY);
Console.Write(border.ToString());
Console.SetCursorPosition(Console.WindowWidth - 1, borderY);
Console.Write(border.ToString());
}
// reset all game variables:
length = 1;
crashed = false;
curDirection = Direction.Up;
eaten.Clear();
x = Console.WindowWidth / 2;
y = Console.WindowHeight / 2;
eaten.Add(x.ToString("00") + y.ToString("00"), true);
// draw new snake:
Console.SetCursorPosition(x, y);
Console.Write(snake);
// wait for initial keypress:
while (!Console.KeyAvailable)
{
System.Threading.Thread.Sleep(10);
}
// see if intitial direction should be changed:
cki = Console.ReadKey(true);
switch (cki.KeyChar)
{
case 'w':
curDirection = Direction.Up;
break;
case 's':
curDirection = Direction.Down;
break;
case 'a':
curDirection = Direction.Left;
break;
case 'd':
curDirection = Direction.Right;
break;
case 'q':
quit = true;
break;
}
// main game loop:
DateTime nextCheck = DateTime.Now.AddMilliseconds(delay);
while (!quit && !crashed)
{
Console.Title = "Length: " + length.ToString();
// consume keystrokes and change current direction until the delay has expired:
// *The snake won't actually move until after the delay!
while (nextCheck > DateTime.Now)
{
if (Console.KeyAvailable)
{
cki = Console.ReadKey(true);
switch (cki.KeyChar)
{
case 'w':
curDirection = Direction.Up;
break;
case 's':
curDirection = Direction.Down;
break;
case 'a':
curDirection = Direction.Left;
break;
case 'd':
curDirection = Direction.Right;
break;
case 'q':
quit = true;
break;
}
}
}
// if the user didn't quit, attempt to move without hitting the border:
if (!quit)
{
string key = "";
switch (curDirection)
{
case Direction.Up:
if (y > 1)
{
y--;
}
else
{
crashed = true;
}
break;
case Direction.Down:
if (y < Console.WindowHeight - 3)
{
y++;
}
else
{
crashed = true;
}
break;
case Direction.Left:
if (x > 1)
{
x--;
}
else
{
crashed = true;
}
break;
case Direction.Right:
if (x < Console.WindowWidth - 2)
{
x++;
}
else
{
crashed = true;
}
break;
}
// if the user didn't hit the border, see if they hit the snake
if (!crashed)
{
key = x.ToString("00") + y.ToString("00");
if (!eaten.ContainsKey(key))
{
length++;
eaten.Add(key, true);
Console.SetCursorPosition(x, y);
Console.Write(snake);
}
else
{
crashed = true;
}
}
// set the next delay:
nextCheck = DateTime.Now.AddMilliseconds(delay);
}
} // end main game loop
if (crashed)
{
Console.Title = "*** Crashed! *** Length: " + length.ToString() + " Hit 'q' to quit, or 'r' to retry!";
// wait for quit or retry:
bool retry = false;
while (!quit && !retry)
{
if (Console.KeyAvailable)
{
cki = Console.ReadKey(true);
switch (cki.KeyChar)
{
case 'q':
quit = true;
break;
case 'r':
retry = true;
break;
}
}
}
}
} // end main program loop
} // end Main()
}
Related
I'm attempting to make a snake game with C# in the console app. I managed to make the player (snake), the grid or area of the playground, the random food spawner, and collisions of the walls of the grid or playground and food, so every time the snake touches the food, it dissapears and spawns in other random location inside of the playground.
Now the next thing to do is to make my snake to be just 1 char, and everytime it eats the food to add 1 more char to it, so it turns into a 2 char snake... and so on, just how the classic snake game works. Right now, the snake is just 1 char, but it prints all over the console when I move it.
How do I do this? I'm kinda stuck and I've read a lot of others people code and explanation, and they talk about FIFO stack, or a queue. Do I need to delete all of my current code of the player (snake) and recreat from scratch using this type of collection? Here is the main code of the player, the other classes are just for the grid or area, console windows resizing and a simple beeping sound whenever the snake eats the food.
I am a complete begginer and this is my first time doing something like this, so if you find a rookie mistake please tell me so I can get better at it. Thank you!
public class Player
{
private Grid grid = new Grid();
private int x = 38;
private int y = 10;
private Random random1 = new Random();
private Random random2 = new Random();
private int foodx = 0;
private int foody = 0;
public void Play()
{
const char toWrite = '*';
Write(toWrite, x, y);
grid.Create();
while (true)
{
foodx = random1.Next(1, 79);
foody = random1.Next(1, 19);
SpawnFood();
while (x != foodx || y != foody)
{
if (Console.KeyAvailable)
{
var command = Console.ReadKey().Key;
switch (command)
{
case ConsoleKey.UpArrow:
if (y > 1)
y--;
break;
case ConsoleKey.DownArrow:
if (y < 19)
y++;
break;
case ConsoleKey.RightArrow:
if (x < 78)
x++;
break;
case ConsoleKey.LeftArrow:
if (x > 1)
x--;
break;
default:
Console.WriteLine("That key is not avilable");
break;
}
Write(toWrite, x, y);
}
}
}
}
public void Write(char toWrite, int x = 0, int y = 0)
{
if (x >= 0 && y >= 0)
{
Console.SetCursorPosition(x, y);
Console.Write('*');
}
}
public void SpawnFood()
{
Console.SetCursorPosition(foodx, foody);
Console.Write('O');
BeepSound.Do();
}
}
}`
I need help with a test method to see if the snake is/or isn't moving in the right direction, aka "==/!=".
I also need to check if the snake has food on the console or not.
I've tried to call the "starter class" that I have almost all the code in. But I can't try to predict where it will go correctly and assert from there.
How should I go about doing so? [Only need tips!]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace Snake
{
/// <summary>
/// Start class, everything goes from here.
/// </summary>
public class Starter : InterF
{
public static object Key { get; private set; }
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.Green;
bool loopC = true;
while (loopC)
{
string mystring = null; Console.WriteLine("Welcome to Snake.\nPress S+Enter to start or press Q+Enter to exit.");
mystring = Console.ReadLine();
switch (mystring)
{
case "Q":
Environment.Exit(0);
break;
case "S":
Console.WriteLine("Starting game!");
System.Threading.Thread.Sleep(4000);
loopC = false;
break;
default:
Console.WriteLine("Invalid Entry.. Try again.");
break;
}
}
//Call for GameSense if
//they want to play.
GameSense();
}
/// <summary>
/// Game object!
/// </summary>
public static void GameSense()
{
//Game console height/width.
Console.WindowHeight = 30;
Console.WindowWidth = 70;
int screenwidth = Console.WindowWidth;
int screenheight = Console.WindowHeight;
Random randomnummer = new Random();
//Lenght of tail == current score.
int score = 2;
int gameover = 0;
//Gather positions from pixel class.
pixel positions = new pixel();
positions.xpos = screenwidth / 2;
positions.ypos = screenheight / 2;
positions.Black = ConsoleColor.Red;
string movement = "RIGHT";
//Position manegment.
List<int> xpos = new List<int>();
List<int> ypos = new List<int>();
int berryx = randomnummer.Next(0, screenwidth);
int berryy = randomnummer.Next(0, screenheight);
//Time manegment.
DateTime time1 = DateTime.Now;
DateTime time2 = DateTime.Now;
string buttonpressed = "no";
//Draw world from GameWorld.cs.
GameWorld.DrawBorder(screenwidth, screenheight);
while (true)
{
GameWorld.ClearConsole(screenwidth, screenheight);
if (positions.xpos == screenwidth - 1 || positions.xpos == 0 || positions.ypos == screenheight - 1 || positions.ypos == 0)
{
gameover = 1;
}
Console.ForegroundColor = ConsoleColor.Green;
if (berryx == positions.xpos && berryy == positions.ypos)
{
score++;
berryx = randomnummer.Next(1, screenwidth - 2);
berryy = randomnummer.Next(1, screenheight - 2);
}
for (int i = 0; i < xpos.Count(); i++)
{
Console.SetCursorPosition(xpos[i], ypos[i]);
Console.Write("*");
if (xpos[i] == positions.xpos && ypos[i] == positions.ypos)
{
gameover = 1;
}
}
if (gameover == 1)
{
break;
}
Console.SetCursorPosition(positions.xpos, positions.ypos);
Console.ForegroundColor = positions.Black;
Console.Write("*");
//Food color & position.
Console.SetCursorPosition(berryx, berryy);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write("*");
Console.CursorVisible = false;
time1 = DateTime.Now;
buttonpressed = "no";
while (true)
{
time2 = DateTime.Now;
if (time2.Subtract(time1).TotalMilliseconds > 500) { break; }
if (Console.KeyAvailable)
{
ConsoleKeyInfo info = Console.ReadKey(true);
if (info.Key.Equals(ConsoleKey.UpArrow) && movement != "DOWN" && buttonpressed == "no")
{
movement = "UP";
buttonpressed = "yes";
}
if (info.Key.Equals(ConsoleKey.DownArrow) && movement != "UP" && buttonpressed == "no")
{
movement = "DOWN";
buttonpressed = "yes";
}
if (info.Key.Equals(ConsoleKey.LeftArrow) && movement != "RIGHT" && buttonpressed == "no")
{
movement = "LEFT";
buttonpressed = "yes";
}
if (info.Key.Equals(ConsoleKey.RightArrow) && movement != "LEFT" && buttonpressed == "no")
{
movement = "RIGHT";
buttonpressed = "yes";
}
}
}
xpos.Add(positions.xpos);
ypos.Add(positions.ypos);
switch (movement)
{
case "UP":
positions.ypos--;
break;
case "DOWN":
positions.ypos++;
break;
case "LEFT":
positions.xpos--;
break;
case "RIGHT":
positions.xpos++;
break;
}
if (xpos.Count() > score)
{
xpos.RemoveAt(0);
ypos.RemoveAt(0);
}
}
Console.SetCursorPosition(screenwidth / 5, screenheight / 2);
Console.WriteLine("Game over, Score: " + score);
Console.SetCursorPosition(screenwidth / 5, screenheight / 2 + 1);
System.Threading.Thread.Sleep(1000);
restart();
}
/// <summary>
/// Restarter.
/// </summary>
public static void restart()
{
string Over = null; Console.WriteLine("\nWould you like to start over? Y/N");
bool O = true;
while (O)
{
Over = Console.ReadLine();
switch (Over)
{
case "Y":
Console.WriteLine("\nRestarting!");
System.Threading.Thread.Sleep(2000);
break;
case "N":
Console.WriteLine("\nThank you for playing!");
Environment.Exit(0);
break;
default:
Console.WriteLine("Invalid Entry.. Try again.");
break;
}
}
}
/// <summary>
/// Set/get pixel position.
/// </summary>
class pixel
{
public int xpos { get; set; }
public int ypos { get; set; }
public ConsoleColor Black { get; set; }
}
}
}
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NUnit.Framework;
using Snake;
using System;
using System.Collections.Generic;
using System.Text;
namespace Snake.Tests
{
[TestClass()]
public class StarterTests
{
[TestMethod()]
public void test()
{
}
}
}
The difficulty you're experiencing is caused mostly because one giant function is basically untestable.
So, you need to split your code. What follows is a list of the things you should consider:
instead of a giant static function, you could create a class GameSense with a Run method. In your main you would do new GameSense.Run(). In the constructor of the class you would put the initialization, and the state of the game (AFAICT, the various variables declared on top of GameSense).
you should split the body of GameSense by purpose: ReadInput(), UpdatePosition(movement), DrawBerry, CheckGameOver, etc
You should limit the usage non-testing friendly things to only the body of the Run method or in the ReadInput method. Then your tests should proceed like this:
// classes should have an uppercase initial. please rename "pixel" to "Pixel"!
// this is the setup
var pos = new pixel();
pos.xpos = 10;
pos.ypos = 10;
// this is the operation:
UpdatePosition(pos, "UP");
// check the results
Assert.That(pos.xpos, Is.EqualTo(10));
Assert.That(pos.ypos, Is.EqualTo(9));
If it seems mundane, it's because tests usually are :D
Is there a way to write a string in specific coordinates in a console window without using Console.SetCursorPosition(?,?)
General idea of solving this problem:
Compute the current "frame" of your game, which is just lines of text, completely
Clear the console
Print the entire "frame" to the console
Wait for input, modify in-game objects, repeat.
E.g., I wrote the following "console game": The player is a "*", the rest is filled with "-". Window adapts to resizes automatically. Player can move in every direction with arrow keys (left,right,up,down). No error checking done.
It's a basic setup that uses an array of StringBuilder objects to be able to do easy string modification (strings are immutable). Would make sense to write a general DrawTextInFramebuffer(string text, int x, int y, framebuffer) though.
Edit: Also, mandatory video suggestion: https://www.youtube.com/watch?v=KkMZI5Jbf18, Retro-Racing game using the console window and colored blocks in C++, though C# implementation would also be possible.
using System;
using System.Linq;
using System.Text;
namespace StackOverflowTesting
{
class Program
{
/* Player position */
static int PlayerX = 1;
static int PlayerY = 1;
static string ComputeFrameBuffer()
{
//What are the current dimensions of the console window
int consoleWindowHeight = Console.WindowHeight;
int consoleWindowWidth = Console.WindowWidth - 1; //-1 prevents line overflow
//Compute framebuffer line-wise
var lines = new StringBuilder[consoleWindowHeight];
for(int y = 0; y < consoleWindowHeight; y++)
{
//Create the line as a repetition of consoleWindowWidth spaces or other filler.
lines[y] = new StringBuilder(string.Join("", Enumerable.Repeat("-", consoleWindowWidth)));
for (int x = 0; x < consoleWindowWidth; x++)
{
//What do we need to draw at this (x,y) position? is the player here?
if(PlayerX == x && PlayerY == y)
{
//Yes. Use a '*' for the player "sprite"..
lines[y][x] = '*';
}
}
}
//Concatinate all lines
return string.Join("\n", lines.Select(l => l.ToString()));
}
static void Main(string[] args)
{
bool runGame = true;
while (runGame)
{
//Render current frame
string frame = ComputeFrameBuffer();
Console.Clear();
Console.Write(frame);
//Grab next user input
var pressedKey = Console.ReadKey(false);
//Handle stuff
switch (pressedKey.Key)
{
case ConsoleKey.LeftArrow:
PlayerX--;
break;
case ConsoleKey.RightArrow:
PlayerX++;
break;
case ConsoleKey.UpArrow:
PlayerY--; //Coordinate system is upper left = (0,0). Downwards increases Y.
break;
case ConsoleKey.DownArrow:
PlayerY++;
break;
case ConsoleKey.Escape:
runGame = false;
break;
}
//clamp coordinates to be always within bounds
int maxY = Console.WindowHeight;
int maxX = Console.WindowWidth - 1;
if (PlayerX < 0) PlayerX = 0;
if (PlayerX >= maxX) PlayerX = maxX - 1;
if (PlayerY < 0) PlayerY = 0;
if(PlayerY >= maxY) PlayerY = maxY - 1;
}
}
}
}
To control the drawing of the character to within the viewable console box, just replace the switch statement with this one:
switch (pressedKey.Key)
{
case ConsoleKey.LeftArrow:
{
PlayerX--;
if (PlayerX == -1)
PlayerX++;
}
break;
case ConsoleKey.RightArrow:
{
PlayerX++;
if(PlayerX == width-1)
PlayerX--;
}
break;
case ConsoleKey.UpArrow:
{
PlayerY--;
if (PlayerY == -1)
PlayerY++;
}
break;
case ConsoleKey.DownArrow:
{
PlayerY++;
if (PlayerY == height)
PlayerY--;
}
break;
case ConsoleKey.E:
runGame = false;
break;
}
I'm trying to make a little game in the Console App and i want an enemy to move at a constantly while moving the player. The code looks like this right now:
public static int y = 5;
public static string player = "O";
public static int enemyX = 10;
static void Main(string[] args)
{
while (true)
{
Console.SetCursorPosition(enemyX, 10);
Console.Write("X");
enemyX = enemyX - 1;
Console.CursorVisible = false;
Console.SetCursorPosition(5, y);
Console.Write(player);
var move = Console.ReadKey(true);
switch (move.Key)
{
case ConsoleKey.W:
y = y - 1;
break;
case ConsoleKey.S:
y = y + 1;
break;
}
if (y <= 0)
{
y = 1;
}
if (y >= 25)
{
y = 24;
}
Console.Clear();
}
}
From what I understand you want the enemy to move regardless of if the player moves or not and to do that you could use Threads
Example:
bool BreakThread = false; //you need this to break the thread loop
Thread enemyThread = new Thread(()=>
{
while(!BreakThread)
{
//do Enemy Actions
}
});
enemyThread.Start();
//then execute your main game loop
but remember to set the BreakThread value to true when ever you
are closing the game.
I want to be able to change between the forms using an IR sensor.
I have created an edge variable that stores the edges detected when moving your hand across it.
So by moving my hands across the sensor I should be able to go back and forth between the forms.
However after the 2nd swipe I just get tons of errors.
This is the part of the code that is not working:
static void g_detected(object sender,PinStatusEventArgs e)
{
edges++;
switch(edges)
{
case 1:
break;
case 2:
edges = 0;
if (weatherView.Visible)
{
weatherView.Visible = false;
stockView.Visible = true;
}
else if (!weatherView.Visible)
{
weatherView.Visible = true;
stockView.Visible = false;
}
break;
}
}
A better approach would be getting rid of checking the incremented variable. Instead we can use mod.
static void g_detected(object sender, PinStatusEventArgs e) {
edges += 1;
switch (edges % 2) {
case 1:
break;
case 0:
if (weatherView.Visible) {
weatherView.Visible = false;
stockView.Visible = true;
} else if (!weatherView.Visible) {
weatherView.Visible = true;
stockView.Visible = false;
}
break;
default:
//Will never hit, just to handle general coding conventions.
break;
}
}