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;
}
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
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'm creating a Galaga type game and I am focusing on the ship's movement. I have this code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Left)
{
moveLeft();
}
else if (e.Key == Key.Right)
{
moveRight();
}
else if (e.Key == Key.Space)
{
bullet();
}
}
private async void bullet()
{
Line bullet = new Line();
bullet.X1 = Canvas.GetLeft(testMovement) + (testMovement.Width / 2);
bullet.Y1 = Canvas.GetTop(testMovement);
bullet.X2 = bullet.X1;
bullet.Y2 = bullet.Y1 + 3;
bullet.Stroke = new SolidColorBrush(Color.FromRgb(255,255,255));
bullet.StrokeThickness = 3;
horizontalMove.Children.Add(bullet);
for (int i = 0; i < 50; i++)
{
await Task.Delay(20);
shoot(bullet);
}
horizontalMove.Children.Remove(bullet);
}
private void shoot(Line bullet)
{
bullet.Y2 = bullet.Y1;
bullet.Y1 -= 5;
}
int x = 0;
private void moveRight()
{
if (x>=0 && x <= 370)
{
if(x == 370)
{
x -= 10;
}
Canvas.SetLeft(testMovement, x += 10);
}
else
{
if (x < 0)
x = 0;
else
x = 370;
}
label.Content = x ;
}
private void moveLeft()
{
if (x>=0 && x <= 370)
{
if (x == 0)
{
x += 10;
}
Canvas.SetLeft(testMovement, x -= 10);
}
else
{
if (x < 0)
x = 0;
else
x = 370;
}
label.Content = x;
}
private void Window_KeyUp(object sender, KeyEventArgs e) {}
}
The problem with this code is that when I change directions, it has a certain delay and the animation is not so smooth.
So, how should I proceed if I want to constantly check for the keyboard's state (for instance, whether left, right or space is clicked) without using the event handler?
With your code, you're waiting for a event to come before moving your player. It's a commonly misunderstood aspect of game development. The manual of SDL library has a great explaination:
Keyboard events only take place when a keys state changes from being
unpressed to pressed, and vice versa.
int alien_x=0, alien_y=0;
/* Main game loop */
/* Check for events */
while( SDL_PollEvent( &event ) ){
switch( event.type ){
/* Look for a keypress */
case SDL_KEYDOWN:
/* Check the SDLKey values and move change the coords */
switch( event.key.keysym.sym ){
case SDLK_LEFT:
alien_x -= 1;
break;
case SDLK_RIGHT:
alien_x += 1;
break;
case SDLK_UP:
alien_y -= 1;
break;
case SDLK_DOWN:
alien_y += 1;
break;
default:
break;
}
}
}
}
At first glance you may think this is a perfectly reasonable piece of
code for the task, but it isn't. Like I said keyboard events only
occur when a key changes state, so the user would have to press and
release the left cursor key 100 times to move the alien 100 pixels to
the left. To get around this problem we must not use the events to
change the position of the alien, we use the events to set flags which
are then used in a seperate section of code to move the alien.
Something like this:
int alien_x=0, alien_y=0;
int alien_xvel=0, alien_yvel=0;
/* Main game loop */
/* Check for events */
while( SDL_PollEvent( &event ) ){
switch( event.type ){
/* Look for a keypress */
case SDL_KEYDOWN:
/* Check the SDLKey values and move change the coords */
switch( event.key.keysym.sym ){
case SDLK_LEFT:
alien_xvel = -1;
break;
case SDLK_RIGHT:
alien_xvel = 1;
break;
case SDLK_UP:
alien_yvel = -1;
break;
case SDLK_DOWN:
alien_yvel = 1;
break;
default:
break;
}
break;
/* We must also use the SDL_KEYUP events to zero the x */
/* and y velocity variables. But we must also be */
/* careful not to zero the velocities when we shouldn't*/
case SDL_KEYUP:
switch( event.key.keysym.sym ){
case SDLK_LEFT:
/* We check to make sure the alien is moving */
/* to the left. If it is then we zero the */
/* velocity. If the alien is moving to the */
/* right then the right key is still press */
/* so we don't tocuh the velocity */
if( alien_xvel < 0 )
alien_xvel = 0;
break;
case SDLK_RIGHT:
if( alien_xvel > 0 )
alien_xvel = 0;
break;
case SDLK_UP:
if( alien_yvel < 0 )
alien_yvel = 0;
break;
case SDLK_DOWN:
if( alien_yvel > 0 )
alien_yvel = 0;
break;
default:
break;
}
break;
default:
break;
}
}
/* Update the alien position */
alien_x += alien_xvel;
alien_y += alien_yvel;
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()
}