Hi! :)
In yesterday's article, HTTP Requests in Wireshark, we used Wireshark to observe the messages sent and received by web browsers when downloading a webpage.
In today's article, we're going to do that ourselves, in code! :D More specifically, we will write a simple client that connects to a web server and downloads a webpage.
Although the HTTP requests sent by a web browser might seem a little complicated, HTTP Made Really Easy shows that it really takes a very short request to retrieve a web page. For example, the following simple request can download the homepage of Programmer's Ranch:
GET / HTTP/1.1
Host: www.programmersranch.com
Note that the above includes a double newline which is essential for the request to be interpreted by the server (refer to yesterday's article).
Start a new SharpDevelop project, and include the necessary libraries for I/O and network programming:
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
We first declare a String to contain the HTTP request, as follows:
String request = @"GET / HTTP/1.1
Host: www.programmersranch.com
Connection: Close
";
This is a special kind of String. The @ before the starting quotes shows that it is a literal string. This means that newlines are included in the string, and we can use it to make multiline strings. I have also included a Connection: Close header field, so that the server will automatically close the connection once it has sent back all the data - this makes it easier for us to know when we have received everything. Finally, note the double-newline at the end of the request, which is important.
Now, this is all the code we need:
using (TcpClient client = new TcpClient("programmersranch.com", 80))
using (StreamWriter writer = new StreamWriter(client.GetStream()))
using (StreamReader reader = new StreamReader(client.GetStream()))
using (StreamWriter outputFile = File.CreateText("webpage.html"))
{
writer.Write(request);
writer.Flush();
String line = String.Empty;
while ((line = reader.ReadLine()) != null)
{
outputFile.WriteLine(line);
}
Console.WriteLine("Webpage has been written to webpage.html");
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
Here we're using TcpClient in order to connect to the website we want, and we are using port 80 since this is HTTP. We also declare a StreamWriter and StreamReader using the TcpClient's stream, so we can easily send data to and receive data from the server. Finally, we open a file called webpage.html to which we will write the received data. Since webpages tend to be quite long nowadays, this is better than writing it to the console window.
Note how the multiple using statements allow us to open and work with several resources, and they are automatically closed at the end.
In the body of the using statements, the first thing we do is send out the HTTP request, and remember to flush (remember the words of wisdom from an earlier article: streams and toilets must always be flushed) the stream to ensure that the request is actually sent.
Then, we receive the response from the server, line by line, and we write that line to the output file (webpage.html). When there is no more data to receive, reader.ReadLine() returns null, and the loop ends.
When you run this program...
...you will find the new file webpage.html in the folder where SharpDevelop puts your compiled executable (normally under bin\Debug in the folder where your source code is):
You can then open the file with your favourite text editor (Notepad++ is a good one) to view the full HTTP response:
You'll notice that the response includes the HTTP header (at the top) and the webpage's HTML, separated by a double-newline. As an exercise, try discarding the HTTP header, leaving only the HTML webpage.
Wonderful! :) In this article we have seen how easy it is to communicate with servers out there, and particularly how easy it is to download a webpage. If you want to learn about HTTP, HTTP Made Really Easy is a great place to start. You can also read an old blog post called "HTTP Communication: A Closer Look" which I had written about certain insights I observed while working on my BSc's Final Year Project. Finally, to learn about the HTTP protocol, there's no better place than RFC2616, which is the official standard.
We will do more network programming here in the future, so check back for more! :)
Tuesday, May 21, 2013
Monday, May 20, 2013
HTTP Requests in Wireshark
Hi everyone! :)
In yesterday's article, Network Programming: Networking Theory, we discussed what happens when a message is sent over a network, and when it is received. Today, we're going to see a practical example of that, by observing the HTTP requests sent by a web browser.
The first thing you should do is download Wireshark. This program will allow you to monitor network traffic going into and out of your PC. After installing it, run it, and you will see the following main screen:
Click on "Capture Options" and tick the checkbox next to the network interface listed. The network interface is basically a network card or, more commonly, the networking hardware on your motherboard. Wireshark can monitor traffic passing through the Ethernet port.
Click the "Start" button to start capturing packets. Immediately, you will start seeing stuff going in and out of your PC. You will know whether it's incoming or outgoing depending on whether your PC's IP address is in the "Source" or "Destination" column (in the screenshot below, my IP address is hidden):
In the "Filter" field at the top, type "http" and press ENTER. This filter allows you to concentrate on a specific type of network traffic - in this case, we are focusing on HTTP traffic which is used by web browsers.
In the Capture menu, Restart capturing, since there is a lot of traffic that doesn't interest us. From a web browser, visit http://www.programmersranch.com/. Soon after, Stop capturing in Wireshark from the Capture menu.
You can now find various HTTP requests to various parts of the page at programmersranch.com, including the page itself and various images. The screenshot above shows the HTTP request for the main page.You can expand the sections towards the middle of the window to view more detail about various parts of the transmission. In this case, I've expanded the HTTP section, where you can see the whole HTTP request. You can do the same for TCP, IP, etc.
When you click on a particular section (such as HTTP), the relevant part of the hex view (at the bottom of the window) is highlighted. This is useful because it sometimes shows you things that you might otherwise miss. In particular, you'll notice that the last four characters are represented by hex values: 0d 0a 0d 0a. In decimal, this becomes 13 10 13 10, which map to the ASCII values of CR LF CR LF (carriage return, line feed, carriage return, line feed). In short, you have two blank lines at the end of the HTTP request. They are important because HTTP requests won't work without them.
You should also be able to find the HTTP response coming from the server, which contains the HTML arriving at your browser (shown above).
Finally, in Wireshark you can right click on a particular transmission and select "Follow TCP Stream":
This allows you to view all the relevant requests and responses on the same connection without having to find the packets one by one:
Be aware, however, that following a TCP stream like this will change the filter from http to something else. This means that you won't be seeing all incoming HTTP packets. Be sure to change the filter back in order to continue viewing HTTP traffic.
Very well. You now know how to use Wireshark to sniff packets going into and out of your PC. In code, you can create the same messages and send them out in a socket in order to achieve the same behaviour that browsers, email clients, etc. have. In tomorrow's article, we will be working with HTTP in code. So stick around. :)
In yesterday's article, Network Programming: Networking Theory, we discussed what happens when a message is sent over a network, and when it is received. Today, we're going to see a practical example of that, by observing the HTTP requests sent by a web browser.
The first thing you should do is download Wireshark. This program will allow you to monitor network traffic going into and out of your PC. After installing it, run it, and you will see the following main screen:
Click on "Capture Options" and tick the checkbox next to the network interface listed. The network interface is basically a network card or, more commonly, the networking hardware on your motherboard. Wireshark can monitor traffic passing through the Ethernet port.
Click the "Start" button to start capturing packets. Immediately, you will start seeing stuff going in and out of your PC. You will know whether it's incoming or outgoing depending on whether your PC's IP address is in the "Source" or "Destination" column (in the screenshot below, my IP address is hidden):
In the "Filter" field at the top, type "http" and press ENTER. This filter allows you to concentrate on a specific type of network traffic - in this case, we are focusing on HTTP traffic which is used by web browsers.
In the Capture menu, Restart capturing, since there is a lot of traffic that doesn't interest us. From a web browser, visit http://www.programmersranch.com/. Soon after, Stop capturing in Wireshark from the Capture menu.
You can now find various HTTP requests to various parts of the page at programmersranch.com, including the page itself and various images. The screenshot above shows the HTTP request for the main page.You can expand the sections towards the middle of the window to view more detail about various parts of the transmission. In this case, I've expanded the HTTP section, where you can see the whole HTTP request. You can do the same for TCP, IP, etc.
When you click on a particular section (such as HTTP), the relevant part of the hex view (at the bottom of the window) is highlighted. This is useful because it sometimes shows you things that you might otherwise miss. In particular, you'll notice that the last four characters are represented by hex values: 0d 0a 0d 0a. In decimal, this becomes 13 10 13 10, which map to the ASCII values of CR LF CR LF (carriage return, line feed, carriage return, line feed). In short, you have two blank lines at the end of the HTTP request. They are important because HTTP requests won't work without them.
You should also be able to find the HTTP response coming from the server, which contains the HTML arriving at your browser (shown above).
Finally, in Wireshark you can right click on a particular transmission and select "Follow TCP Stream":
This allows you to view all the relevant requests and responses on the same connection without having to find the packets one by one:
Be aware, however, that following a TCP stream like this will change the filter from http to something else. This means that you won't be seeing all incoming HTTP packets. Be sure to change the filter back in order to continue viewing HTTP traffic.
Very well. You now know how to use Wireshark to sniff packets going into and out of your PC. In code, you can create the same messages and send them out in a socket in order to achieve the same behaviour that browsers, email clients, etc. have. In tomorrow's article, we will be working with HTTP in code. So stick around. :)
Saturday, May 18, 2013
Network Programming: Networking Theory
Hi all! :)
In yesterday's article (C# Network Programming: Echo Client/Server), we saw a simple example of a client and server communicating together by means of a simple protocol. However, a lot of questions remained unanswered.
Today we're going to learn a bit more about how the internet actually works, and that will help understand network programming better.
Let's say we have the setting above: the laptop on the left is connected to the server on the right. As we have seen yesterday, the laptop (client) must know the server's IP address and port in order to connect to it. The IP address and port together form an endpoint or socket.
The client must also have an endpoint of its own, to form such a connection. But since it is a client, the port is assigned automatically by the operating system - which is why we normally don't see it in network programming.
A port can be anything between 0 and 65535, but the first 1024 are reserved for standard services (such as HTTP or email), so we normally use ports 1024 onwards for our custom programs. By using different ports, a single computer (i.e. a single IP address) may have several different incoming and outgoing connections at the same time.
The internet works a little bit like the postal system. If you want to send someone a letter, you normally put it in an envelope, and write the person's address on the envelope. The postal system will then find a way to deliver your letter. In the case of the internet, this happens mostly thanks to TCP/IP (TCP over IP). The IP protocol (which is where IP addresses come from) takes care of routing a message from one computer to another - it can pass through several other routers/servers on the way.
While IP can find a route between the sending and receiving computers, another protocol (TCP or UDP) must be used to deliver the message to the correct application on the destination computer (using ports). While TCP is normally preferred because it allows reliable message delivery, UDP is simpler and useful in certain applications (e.g. video streaming, where the loss of a little bit of data is better than waiting for it to be retransmitted).
IP, TCP and UDP are part of a bigger picture called the OSI model, which categorises internet protocols. It looks something like this:
Let's read this table top-down. Your web browser can download web pages by sending an HTTP request to Google. This HTTP request passes to the transport layer, where a TCP header is added containing the source and destination port and other stuff. The result is passed to the network layer, where an IP header is added containing the source and destination port among other stuff. This is then broken up into pieces based on the Ethernet protocol, which works using hardware MAC addresses. Finally, the pieces are converted into electrical signals (representing bits and bytes) and sent out over the wire.
At the receiving end, this works in the opposite direction (bottom-up). The bits and bytes received over the wire are assembled into Ethernet frames. From these Ethernet frames, one or more IP packets are extracted. The IP header is removed, and the result is passed to the transport layer. The TCP header is removed, and from the information therein, the original HTTP request can be forwarded to the appropriate port on the receiving machine. It is then up to the application listening on that particular port (in this case a web server) to deal with the request appropriately (in this case by sending back an HTTP response).
Okay. So this was just a very brief summary of how the internet works... but you should at least realise the difference between the World Wide Web and the Internet. If you remember that the WWW is based on HTTP and port 80, you will realise that it is just one of countless services on the internet.
In tomorrow's article, we will see a practical example of how all this works.
In yesterday's article (C# Network Programming: Echo Client/Server), we saw a simple example of a client and server communicating together by means of a simple protocol. However, a lot of questions remained unanswered.
Today we're going to learn a bit more about how the internet actually works, and that will help understand network programming better.
Let's say we have the setting above: the laptop on the left is connected to the server on the right. As we have seen yesterday, the laptop (client) must know the server's IP address and port in order to connect to it. The IP address and port together form an endpoint or socket.
The client must also have an endpoint of its own, to form such a connection. But since it is a client, the port is assigned automatically by the operating system - which is why we normally don't see it in network programming.
A port can be anything between 0 and 65535, but the first 1024 are reserved for standard services (such as HTTP or email), so we normally use ports 1024 onwards for our custom programs. By using different ports, a single computer (i.e. a single IP address) may have several different incoming and outgoing connections at the same time.
The internet works a little bit like the postal system. If you want to send someone a letter, you normally put it in an envelope, and write the person's address on the envelope. The postal system will then find a way to deliver your letter. In the case of the internet, this happens mostly thanks to TCP/IP (TCP over IP). The IP protocol (which is where IP addresses come from) takes care of routing a message from one computer to another - it can pass through several other routers/servers on the way.
While IP can find a route between the sending and receiving computers, another protocol (TCP or UDP) must be used to deliver the message to the correct application on the destination computer (using ports). While TCP is normally preferred because it allows reliable message delivery, UDP is simpler and useful in certain applications (e.g. video streaming, where the loss of a little bit of data is better than waiting for it to be retransmitted).
IP, TCP and UDP are part of a bigger picture called the OSI model, which categorises internet protocols. It looks something like this:
|
OSI Model Layer
|
Data Chunks
|
Say what?
|
|
Application
|
Application Data
|
HTTP, email, etc
|
|
Presentation
|
||
|
Session
|
||
|
Transport
|
Segments
|
TCP or UDP
|
|
Network
|
Packets/Datagrams
|
IP
|
|
Data Link
|
Frames
|
Ethernet
|
|
Physical
|
Bits/Bytes
|
Wired or Wireless
|
Let's read this table top-down. Your web browser can download web pages by sending an HTTP request to Google. This HTTP request passes to the transport layer, where a TCP header is added containing the source and destination port and other stuff. The result is passed to the network layer, where an IP header is added containing the source and destination port among other stuff. This is then broken up into pieces based on the Ethernet protocol, which works using hardware MAC addresses. Finally, the pieces are converted into electrical signals (representing bits and bytes) and sent out over the wire.
At the receiving end, this works in the opposite direction (bottom-up). The bits and bytes received over the wire are assembled into Ethernet frames. From these Ethernet frames, one or more IP packets are extracted. The IP header is removed, and the result is passed to the transport layer. The TCP header is removed, and from the information therein, the original HTTP request can be forwarded to the appropriate port on the receiving machine. It is then up to the application listening on that particular port (in this case a web server) to deal with the request appropriately (in this case by sending back an HTTP response).
Okay. So this was just a very brief summary of how the internet works... but you should at least realise the difference between the World Wide Web and the Internet. If you remember that the WWW is based on HTTP and port 80, you will realise that it is just one of countless services on the internet.
In tomorrow's article, we will see a practical example of how all this works.
C# Network Programming: Echo Client/Server
Hola! :)
In today's article we're going to learn about network programming. That means you can have two (or more) machines talking to each other.
I have been doing network programming since 2007, and I can tell you it is awesome! :D This was one of my early projects:
This was a pacman game over a Google Maps setting when Google Android was still in its infancy. You can do a lot of cool stuff when computers interact with each other.
Today we'll write two small programs and have them communicate with each other. In order to do network programming, you will need to use the following libraries:
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
After adding the above in a new console application project, put in the following code:
IPAddress ip = IPAddress.Any;
int port = 18000;
TcpListener server = new TcpListener(ip, port);
server.Start();
TcpClient client = server.AcceptTcpClient();
In network programming, you normally have a server, and any number of clients. The clients can connect to the server because they know its IP address and port. The IP address is a number identifying the machine (such as 192.168.5.185), and the port is a number used to connect to a particular server program (e.g. HTTP servers use port 80; SSH servers use port 22).
In the code above, we are simply starting a server and setting it to listen for connections on port 18000. The IP address is not important since it's the same as the machine running the program - so we set it to IPAddress.Any. A TCPListener is an actual server object: it allows us to accept connections from other machines and work with them. The TCPListener is started and then waits for a client to connect to it. When this happens, we obtain a TCPClient object. We can then talk to this client by obtaining its NetworkStream:
NetworkStream stream = client.GetStream();
We can use this network stream the same way we did with files:
using (StreamReader reader = new StreamReader(stream))
using (StreamWriter writer = new StreamWriter(stream))
{
String line = reader.ReadLine();
Console.WriteLine("Client said: {0}", line);
writer.WriteLine(line);
}
What we do here is wait for a line of text to arrive from the client that connected earlier, and store it in the line variable. After showing what we received, we use the StreamWriter to send back the same line of text.
If you press F5 now, all you get is a blank window: the program isn't doing anything while waiting for a connection.
Start a new console application for the client. Again, make sure you are using the correct libraries:
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
Now, add code to connect to the server:
TcpClient client = new TcpClient("127.0.0.1", 18000);
The IP address 127.0.0.1 is special and means you are connecting on the same machine. If you are running the server on a different machine, you will need to change the IP address in the code above.
Next, we obtain the client's NetworkStream, as we did earlier for the server:
NetworkStream stream = client.GetStream();
We can now use it to talk to the server:
using (StreamReader reader = new StreamReader(stream))
using (StreamWriter writer = new StreamWriter(stream))
{
Console.WriteLine("Write something to send to server:");
String input = Console.ReadLine();
writer.WriteLine(input);
writer.Flush();
String response = reader.ReadLine();
Console.WriteLine("Server said: {0}", response);
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
When the user types something and presses ENTER, it is stored in the input variable. We then use writer.WriteLine(input) to send the input to the server.
The writer.Flush() is very important. If you leave it out, the program will be stuck and send nothing to the server. Like most input/output (I/O), network streams are buffered. That means they usually wait to have a certain amount of data before actually sending it out. The Flush() call forces the data to be sent.
Always remember: streams and toilets must always be flushed.
When the server sends back its response, we store it in the response variable, and show it in the console window. If you run this program now, you get the following exception:
Well duh, that's because the server is not running. So go back to your first (server) program and leave it running. Then, run the second (client) program:
Amazing! :D You have just manage to make two programs talk to each other. If you haven't already, try putting the server on one machine and the client on another (don't forget to change the IP address in the client).
What we have done here is an example of a simple protocol. A protocol consists of the rules by which computers talk to each other. In this case:
As you can see, it is very easy to write network programs in C# (not so much in other languages, such as C). Stick around, because there is much more to learn about network programming, and I will be writing several other articles on the topic that go into more detail and show you how to do certain things (e.g. download email or webpages).
In today's article we're going to learn about network programming. That means you can have two (or more) machines talking to each other.
I have been doing network programming since 2007, and I can tell you it is awesome! :D This was one of my early projects:
This was a pacman game over a Google Maps setting when Google Android was still in its infancy. You can do a lot of cool stuff when computers interact with each other.
Today we'll write two small programs and have them communicate with each other. In order to do network programming, you will need to use the following libraries:
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
After adding the above in a new console application project, put in the following code:
IPAddress ip = IPAddress.Any;
int port = 18000;
TcpListener server = new TcpListener(ip, port);
server.Start();
TcpClient client = server.AcceptTcpClient();
In network programming, you normally have a server, and any number of clients. The clients can connect to the server because they know its IP address and port. The IP address is a number identifying the machine (such as 192.168.5.185), and the port is a number used to connect to a particular server program (e.g. HTTP servers use port 80; SSH servers use port 22).
In the code above, we are simply starting a server and setting it to listen for connections on port 18000. The IP address is not important since it's the same as the machine running the program - so we set it to IPAddress.Any. A TCPListener is an actual server object: it allows us to accept connections from other machines and work with them. The TCPListener is started and then waits for a client to connect to it. When this happens, we obtain a TCPClient object. We can then talk to this client by obtaining its NetworkStream:
NetworkStream stream = client.GetStream();
We can use this network stream the same way we did with files:
using (StreamReader reader = new StreamReader(stream))
using (StreamWriter writer = new StreamWriter(stream))
{
String line = reader.ReadLine();
Console.WriteLine("Client said: {0}", line);
writer.WriteLine(line);
}
What we do here is wait for a line of text to arrive from the client that connected earlier, and store it in the line variable. After showing what we received, we use the StreamWriter to send back the same line of text.
If you press F5 now, all you get is a blank window: the program isn't doing anything while waiting for a connection.
Start a new console application for the client. Again, make sure you are using the correct libraries:
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
Now, add code to connect to the server:
TcpClient client = new TcpClient("127.0.0.1", 18000);
The IP address 127.0.0.1 is special and means you are connecting on the same machine. If you are running the server on a different machine, you will need to change the IP address in the code above.
Next, we obtain the client's NetworkStream, as we did earlier for the server:
NetworkStream stream = client.GetStream();
We can now use it to talk to the server:
using (StreamReader reader = new StreamReader(stream))
using (StreamWriter writer = new StreamWriter(stream))
{
Console.WriteLine("Write something to send to server:");
String input = Console.ReadLine();
writer.WriteLine(input);
writer.Flush();
String response = reader.ReadLine();
Console.WriteLine("Server said: {0}", response);
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
When the user types something and presses ENTER, it is stored in the input variable. We then use writer.WriteLine(input) to send the input to the server.
The writer.Flush() is very important. If you leave it out, the program will be stuck and send nothing to the server. Like most input/output (I/O), network streams are buffered. That means they usually wait to have a certain amount of data before actually sending it out. The Flush() call forces the data to be sent.
Always remember: streams and toilets must always be flushed.
When the server sends back its response, we store it in the response variable, and show it in the console window. If you run this program now, you get the following exception:
Well duh, that's because the server is not running. So go back to your first (server) program and leave it running. Then, run the second (client) program:
Amazing! :D You have just manage to make two programs talk to each other. If you haven't already, try putting the server on one machine and the client on another (don't forget to change the IP address in the client).
What we have done here is an example of a simple protocol. A protocol consists of the rules by which computers talk to each other. In this case:
- Client connects to server.
- Client sends a line of text to server.
- Server sends back that same line of text.
This is called an echo protocol, because the server echoes what the client says. Something of this sort is actually a standard echo protocol (RFC862) intended mostly for debugging.
Naturally, what we did here is very simple. Many standard protocols, such as IMAP (used for email), can get very complicated. Also, you'll notice that a new server must be run in order to handle each new client. We'll deal with this another time. Finally, if you have been following the above code carefully, you'll notice that I didn't Flush() the stream in the server program, even though I was writing data to it. That's because the stream is automatically closed because of the using statement. When that happens, any data in the stream is flushed, so we don't need to do that in code.
As you can see, it is very easy to write network programs in C# (not so much in other languages, such as C). Stick around, because there is much more to learn about network programming, and I will be writing several other articles on the topic that go into more detail and show you how to do certain things (e.g. download email or webpages).
Thursday, May 16, 2013
C# Threading: Bouncing Ball
Hi all! :)
I hope you've enjoyed the articles so far, and found the little ASCII art games both entertaining and mentally stimulating. Towards the end of yesterday's article, C# Basics: Snake Game in ASCII Art (Part 2), we discussed the problems with the Snake game. One of them was that the snake wouldn't move on its own - you had to press a key in order to make the program do something.
In today's article, we're going to begin learning about threads, and use them to overcome this limitation. This time we're going to draw a ball bouncing all over the console window.
Start a new console application, and add the following struct before the class Program:
struct Vector
{
public int X;
public int Y;
public Vector(int x, int y)
{
this.X = x;
this.Y = y;
}
}
You'll notice this is exactly the same as the Location struct in C# Basics: Snake Game in ASCII Art (Part 1). Instead of just using it just as a location, we're also going to use it as a direction. You'll see what I mean in a minute. Add the following for starters:
Console.OutputEncoding = System.Text.Encoding.GetEncoding(1252);
Console.Title = "Bouncing Ball";
Vector ballLocation = new Vector(40, 12);
Vector direction = new Vector(-1, -1);
So ballLocation is the location in the console window where we're going to draw our ball. The direction represents where the ball will go next, relative to ballLocation. In this case, the ball starts at (40, 12) and moves in a northwest direction, so the next position will be (39, 11), and then (38, 10), and so on.
[This paragraph is a bit more advanced... feel free to skip it.] The Vector struct is simply a code representation of a mathematical vector, where a vector such as (3, 1) means "three steps to the right, one step down" (depending on what coordinate system you're using). I'm not going to get into the mathematics, but if you're interested, check out my "A Concise Introduction to Vectors" [PDF].
We're now going to add a loop to show the ball and then move it:
while (true)
{
Console.Clear();
Console.SetCursorPosition(ballLocation.X, ballLocation.Y);
Console.Write((char) 4);
ballLocation.X += direction.X;
ballLocation.Y += direction.Y;
Thread.Sleep(100);
}
At the beginning, add the following which is needed by Thread:
using System.Threading;
The code above is an infinite loop; there no condition for which the while loop will end. In this loop, we show an ASCII diamond as our ball, and then simply add the direction to the ballLocation in order to move it. The Thread.Sleep() at the end is simply a delay (in milliseconds) so that you can see the ball move; otherwise it would move quickly. Try changing the value of 100 and use something else (e.g. 1000, which means 1 second) to see what happens.
Press F5 to see the ball move:
You will get an exception once the ball moves off the edge. Add the following to fix this and make the ball change direction when it hits an edge (note || means "OR"):
if (ballLocation.X == 0 || ballLocation.X == 79)
direction.X = -direction.X;
if (ballLocation.Y == 0 || ballLocation.Y == 24)
direction.Y = -direction.Y;
The infinite loop allows the ball to move on its own without user intervention - just as we wanted with Snake. However, there is a problem: we cannot accept any user input like this. In fact, you can only exit by actually closing the window. If you try to add a
Console.ReadKey(true);
at the end of the while loop, we're back to square one: the user must press a key for anything to happen. Clearly, in order to accept input, we need the program to do two things at the same time: let the ball move, and handle user input. Fortunately, such godlike powers are not unique to Chuck Norris and Multiple Man.
A program (or process to be more precise) can do several things at the same time. It can be composed of multiple threads which run at the same time but do different things. This area of programming is called multithreading. Let's see how we can exploit this to allow user input while the ball is moving on its own.
First, move the while loop and the vectors to a new method outside of Main():
public static void Move()
{
Vector ballLocation = new Vector(40, 12);
Vector direction = new Vector(-1, -1);
while (true)
{
Console.Clear();
Console.SetCursorPosition(ballLocation.X, ballLocation.Y);
Console.Write((char) 4);
ballLocation.X += direction.X;
ballLocation.Y += direction.Y;
Thread.Sleep(100);
if (ballLocation.X == 0 || ballLocation.X == 79)
direction.X = -direction.X;
if (ballLocation.Y == 0 || ballLocation.Y == 24)
direction.Y = -direction.Y;
}
}
In what remains of the Main() method, add the following code:
Thread thread = new Thread(Move);
thread.IsBackground = true;
thread.Start();
Console.ReadKey(true);
When you press F5 and run the program, you will now end up with two threads:
The new thread is created in the first line of code above. A thread executes the code in a particular method, so we supply the name of the Move() method (without brackets) as a parameter.
In the second line, we set thread.IsBackground = true. A process (running instance of a program) runs as long as there is a foreground thread (such as the main thread) running. Since our new thread is designated as a background thread, the process will terminate once the main thread has exited.
The rest of the Main() method then starts the secondary thread and waits for a keypress. When the keypress is received, Main() ends, and with it the process. If we didn't set the secondary thread as a background thread, the process would keep on running anyway. Try that out. :)
Fantastic. :) In this article, we learned how to use a simple thread in order to allow a program to do two things at the same time. We used this to allow a game to run, while at the same time waiting for user input.
Stay tuned for more! :)
I hope you've enjoyed the articles so far, and found the little ASCII art games both entertaining and mentally stimulating. Towards the end of yesterday's article, C# Basics: Snake Game in ASCII Art (Part 2), we discussed the problems with the Snake game. One of them was that the snake wouldn't move on its own - you had to press a key in order to make the program do something.
In today's article, we're going to begin learning about threads, and use them to overcome this limitation. This time we're going to draw a ball bouncing all over the console window.
Start a new console application, and add the following struct before the class Program:
struct Vector
{
public int X;
public int Y;
public Vector(int x, int y)
{
this.X = x;
this.Y = y;
}
}
You'll notice this is exactly the same as the Location struct in C# Basics: Snake Game in ASCII Art (Part 1). Instead of just using it just as a location, we're also going to use it as a direction. You'll see what I mean in a minute. Add the following for starters:
Console.OutputEncoding = System.Text.Encoding.GetEncoding(1252);
Console.Title = "Bouncing Ball";
Vector ballLocation = new Vector(40, 12);
Vector direction = new Vector(-1, -1);
So ballLocation is the location in the console window where we're going to draw our ball. The direction represents where the ball will go next, relative to ballLocation. In this case, the ball starts at (40, 12) and moves in a northwest direction, so the next position will be (39, 11), and then (38, 10), and so on.
[This paragraph is a bit more advanced... feel free to skip it.] The Vector struct is simply a code representation of a mathematical vector, where a vector such as (3, 1) means "three steps to the right, one step down" (depending on what coordinate system you're using). I'm not going to get into the mathematics, but if you're interested, check out my "A Concise Introduction to Vectors" [PDF].
We're now going to add a loop to show the ball and then move it:
while (true)
{
Console.Clear();
Console.SetCursorPosition(ballLocation.X, ballLocation.Y);
Console.Write((char) 4);
ballLocation.X += direction.X;
ballLocation.Y += direction.Y;
Thread.Sleep(100);
}
At the beginning, add the following which is needed by Thread:
using System.Threading;
The code above is an infinite loop; there no condition for which the while loop will end. In this loop, we show an ASCII diamond as our ball, and then simply add the direction to the ballLocation in order to move it. The Thread.Sleep() at the end is simply a delay (in milliseconds) so that you can see the ball move; otherwise it would move quickly. Try changing the value of 100 and use something else (e.g. 1000, which means 1 second) to see what happens.
Press F5 to see the ball move:
You will get an exception once the ball moves off the edge. Add the following to fix this and make the ball change direction when it hits an edge (note || means "OR"):
if (ballLocation.X == 0 || ballLocation.X == 79)
direction.X = -direction.X;
if (ballLocation.Y == 0 || ballLocation.Y == 24)
direction.Y = -direction.Y;
The infinite loop allows the ball to move on its own without user intervention - just as we wanted with Snake. However, there is a problem: we cannot accept any user input like this. In fact, you can only exit by actually closing the window. If you try to add a
Console.ReadKey(true);
at the end of the while loop, we're back to square one: the user must press a key for anything to happen. Clearly, in order to accept input, we need the program to do two things at the same time: let the ball move, and handle user input. Fortunately, such godlike powers are not unique to Chuck Norris and Multiple Man.
A program (or process to be more precise) can do several things at the same time. It can be composed of multiple threads which run at the same time but do different things. This area of programming is called multithreading. Let's see how we can exploit this to allow user input while the ball is moving on its own.
First, move the while loop and the vectors to a new method outside of Main():
public static void Move()
{
Vector ballLocation = new Vector(40, 12);
Vector direction = new Vector(-1, -1);
while (true)
{
Console.Clear();
Console.SetCursorPosition(ballLocation.X, ballLocation.Y);
Console.Write((char) 4);
ballLocation.X += direction.X;
ballLocation.Y += direction.Y;
Thread.Sleep(100);
if (ballLocation.X == 0 || ballLocation.X == 79)
direction.X = -direction.X;
if (ballLocation.Y == 0 || ballLocation.Y == 24)
direction.Y = -direction.Y;
}
}
In what remains of the Main() method, add the following code:
Thread thread = new Thread(Move);
thread.IsBackground = true;
thread.Start();
Console.ReadKey(true);
When you press F5 and run the program, you will now end up with two threads:
In the second line, we set thread.IsBackground = true. A process (running instance of a program) runs as long as there is a foreground thread (such as the main thread) running. Since our new thread is designated as a background thread, the process will terminate once the main thread has exited.
The rest of the Main() method then starts the secondary thread and waits for a keypress. When the keypress is received, Main() ends, and with it the process. If we didn't set the secondary thread as a background thread, the process would keep on running anyway. Try that out. :)
Fantastic. :) In this article, we learned how to use a simple thread in order to allow a program to do two things at the same time. We used this to allow a game to run, while at the same time waiting for user input.
Stay tuned for more! :)
Wednesday, May 15, 2013
C# Basics: Snake Game in ASCII Art (Part 2)
Hi all! :)
In yesterday's article, C# Basics: Snake Game in ASCII Art (Part 1), we created the first part of our Snake Ranch game (a clone of Snake) and learned to use structs. Today we will learn to use lists in order to allow the snake to grow.
At the beginning of yesterday's article, we mentioned that we needed to store each part of the snake in a list, as follows:
The head of the snake would be at (1,1), for example. The remaining parts trail behind it in sequence. We can store these locations in a list, in the following fashion:
There is a List collection in .NET that allows us to do this kind of thing. As with dictionaries (see "C# Basics: Morse Code Converter Using Dictionaries", we first need to include System.Collections.Generic first:
using System.Collections.Generic;
We can now declare a new List of Location objects, and put the head as the first item:
List<Location> snake = new List<Location>();
snake.Add(head);
We now change the code that shows the head (from yesterday's article) to show the entire snake in the list:
// show snake
foreach (Location location in snake)
{
Console.SetCursorPosition(location.X, location.Y);
Console.ForegroundColor = ConsoleColor.White;
Console.Write((char) 178);
Console.ResetColor();
}
All we're doing here is going over each element in the list and showing a portion of the snake there. For now we only have the head though.
Before we make the snake grow, we first need to understand what happens when it moves. Consider this again:
Let's say the head of the snake is at (1,1), and it moves to the left. Then the head becomes (0,1). The tail (last part) of the snake goes away, and all the parts between the new head and the old tail remain unchanged. We need to make some changes to our code to handle this properly. First, before the while loop, we declare a new Location variable called next:
Location next;
This will hold the position of the new head, based on the keypress received. In the example above, the head is currently at (1,1), but if the user presses the left arrow, then we want next to contain (0,1).
At the beginning of the while loop, we add the following:
next = snake[0];
Console.Clear();
The first line assigns next to the location of the current head, stored in the first element of the snake variable. Lists can be accessed using [] notation just like arrays.
Next, we change the input handling logic to modify next instead of head:
switch(keyInfo.Key)
{
case ConsoleKey.Escape:
return;
case ConsoleKey.UpArrow:
if (next.Y > 0)
next.Y--;
break;
case ConsoleKey.DownArrow:
if (next.Y < 24)
next.Y++;
break;
case ConsoleKey.LeftArrow:
if (next.X > 0)
next.X--;
break;
case ConsoleKey.RightArrow:
if (next.X < 79)
next.X++;
break;
}
After that, we can do the following:
snake.Insert(0, next);
snake.RemoveAt(snake.Count - 1);
Here we are doing exactly what I explained earlier: adding a new head at the beginning of the snake, and removing the old tail. If you press F5 now, you should have functionality just like what we had at the end of yesterday's article (because we still have just one part of the snake):
All we have left to do now is to make the snake get longer when he eats a star. At the beginning, add a new variable to store the location of the star:
Location star = new Location(60, 20);
After showing the snake, add the following code to show the star:
// show star
Console.SetCursorPosition(star.X, star.Y);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write('*');
Console.ResetColor();
Finally, replace the last two lines in the while loop (Insert() and RemoveAt()) with the following logic:
snake.Insert(0, next);
if (next.X == star.X && next.Y == star.Y)
{
Random random = new Random();
star.X = random.Next(0, 80);
star.Y = random.Next(0, 25);
}
else
snake.RemoveAt(snake.Count - 1);
This code is based on the code from Chase the Star (see "C#: ASCII Art Game (Part 2)"). If the snake bumps into the star, the star moves away to some other random location. Here we are using structs instead of separate starX and starY variables.
The most important thing to understand here is that if the player bumps into the star, we don't remove the snake's tail, thus allowing it to grow. We can now test it:
Awesome! :) We have just made our own variant of Snake using ASCII art. If you test it thoroughly, you'll notice there are a few problems:
As you can see, it can be quite complicated to make a complete game, as there are many things you need to take care of. Making a fully blown Snake game would take many more articles, so I'll leave it at that. In this article we use generic lists to implement the Snake concept. If you're really adventurous, you might want to try and fixing some of the issues above as an exercise. Don't worry if you don't manage - just thinking about strategies to solve them will teach you how to think like a programmer.
I hope you enjoyed this, and come back for more programming articles! :)
Here is the full code for Snake Ranch:
using System;
using System.Collections.Generic;
namespace CsSnake
{
struct Location
{
public int X;
public int Y;
public Location(int x, int y)
{
this.X = x;
this.Y = y;
}
};
class Program
{
public static void Main(string[] args)
{
Console.OutputEncoding = System.Text.Encoding.GetEncoding(1252);
Console.Title = "Snake Ranch";
Location head = new Location(40, 12);
List<Location> snake = new List<Location>();
snake.Add(head);
Location next;
Location star = new Location(60, 20);
while (true)
{
next = snake[0];
Console.Clear();
// show snake
foreach (Location location in snake)
{
Console.SetCursorPosition(location.X, location.Y);
Console.ForegroundColor = ConsoleColor.White;
Console.Write((char) 178);
Console.ResetColor();
}
// show star
Console.SetCursorPosition(star.X, star.Y);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write('*');
Console.ResetColor();
// handle input
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
switch(keyInfo.Key)
{
case ConsoleKey.Escape:
return;
case ConsoleKey.UpArrow:
if (next.Y > 0)
next.Y--;
break;
case ConsoleKey.DownArrow:
if (next.Y < 24)
next.Y++;
break;
case ConsoleKey.LeftArrow:
if (next.X > 0)
next.X--;
break;
case ConsoleKey.RightArrow:
if (next.X < 79)
next.X++;
break;
}
snake.Insert(0, next);
if (next.X == star.X && next.Y == star.Y)
{
Random random = new Random();
star.X = random.Next(0, 80);
star.Y = random.Next(0, 25);
}
else
snake.RemoveAt(snake.Count - 1);
}
}
}
}
In yesterday's article, C# Basics: Snake Game in ASCII Art (Part 1), we created the first part of our Snake Ranch game (a clone of Snake) and learned to use structs. Today we will learn to use lists in order to allow the snake to grow.
At the beginning of yesterday's article, we mentioned that we needed to store each part of the snake in a list, as follows:
The head of the snake would be at (1,1), for example. The remaining parts trail behind it in sequence. We can store these locations in a list, in the following fashion:
There is a List collection in .NET that allows us to do this kind of thing. As with dictionaries (see "C# Basics: Morse Code Converter Using Dictionaries", we first need to include System.Collections.Generic first:
using System.Collections.Generic;
We can now declare a new List of Location objects, and put the head as the first item:
List<Location> snake = new List<Location>();
snake.Add(head);
We now change the code that shows the head (from yesterday's article) to show the entire snake in the list:
// show snake
foreach (Location location in snake)
{
Console.SetCursorPosition(location.X, location.Y);
Console.ForegroundColor = ConsoleColor.White;
Console.Write((char) 178);
Console.ResetColor();
}
All we're doing here is going over each element in the list and showing a portion of the snake there. For now we only have the head though.
Before we make the snake grow, we first need to understand what happens when it moves. Consider this again:
Let's say the head of the snake is at (1,1), and it moves to the left. Then the head becomes (0,1). The tail (last part) of the snake goes away, and all the parts between the new head and the old tail remain unchanged. We need to make some changes to our code to handle this properly. First, before the while loop, we declare a new Location variable called next:
Location next;
This will hold the position of the new head, based on the keypress received. In the example above, the head is currently at (1,1), but if the user presses the left arrow, then we want next to contain (0,1).
At the beginning of the while loop, we add the following:
next = snake[0];
Console.Clear();
The first line assigns next to the location of the current head, stored in the first element of the snake variable. Lists can be accessed using [] notation just like arrays.
Next, we change the input handling logic to modify next instead of head:
switch(keyInfo.Key)
{
case ConsoleKey.Escape:
return;
case ConsoleKey.UpArrow:
if (next.Y > 0)
next.Y--;
break;
case ConsoleKey.DownArrow:
if (next.Y < 24)
next.Y++;
break;
case ConsoleKey.LeftArrow:
if (next.X > 0)
next.X--;
break;
case ConsoleKey.RightArrow:
if (next.X < 79)
next.X++;
break;
}
After that, we can do the following:
snake.Insert(0, next);
snake.RemoveAt(snake.Count - 1);
Here we are doing exactly what I explained earlier: adding a new head at the beginning of the snake, and removing the old tail. If you press F5 now, you should have functionality just like what we had at the end of yesterday's article (because we still have just one part of the snake):
All we have left to do now is to make the snake get longer when he eats a star. At the beginning, add a new variable to store the location of the star:
Location star = new Location(60, 20);
After showing the snake, add the following code to show the star:
// show star
Console.SetCursorPosition(star.X, star.Y);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write('*');
Console.ResetColor();
Finally, replace the last two lines in the while loop (Insert() and RemoveAt()) with the following logic:
snake.Insert(0, next);
if (next.X == star.X && next.Y == star.Y)
{
Random random = new Random();
star.X = random.Next(0, 80);
star.Y = random.Next(0, 25);
}
else
snake.RemoveAt(snake.Count - 1);
This code is based on the code from Chase the Star (see "C#: ASCII Art Game (Part 2)"). If the snake bumps into the star, the star moves away to some other random location. Here we are using structs instead of separate starX and starY variables.
The most important thing to understand here is that if the player bumps into the star, we don't remove the snake's tail, thus allowing it to grow. We can now test it:
Awesome! :) We have just made our own variant of Snake using ASCII art. If you test it thoroughly, you'll notice there are a few problems:
- The snake doesn't move on its own as time goes by. You need to press an arrow key for it to move.
- The snake can bump into itself or the edges and nothing bad happens.
- If the snake moves into the edge, it sort of compresses. When you move away, it regains its former length.
- The star can appear over the snake.
- No obstacles... boring!
As you can see, it can be quite complicated to make a complete game, as there are many things you need to take care of. Making a fully blown Snake game would take many more articles, so I'll leave it at that. In this article we use generic lists to implement the Snake concept. If you're really adventurous, you might want to try and fixing some of the issues above as an exercise. Don't worry if you don't manage - just thinking about strategies to solve them will teach you how to think like a programmer.
I hope you enjoyed this, and come back for more programming articles! :)
Here is the full code for Snake Ranch:
using System;
using System.Collections.Generic;
namespace CsSnake
{
struct Location
{
public int X;
public int Y;
public Location(int x, int y)
{
this.X = x;
this.Y = y;
}
};
class Program
{
public static void Main(string[] args)
{
Console.OutputEncoding = System.Text.Encoding.GetEncoding(1252);
Console.Title = "Snake Ranch";
Location head = new Location(40, 12);
List<Location> snake = new List<Location>();
snake.Add(head);
Location next;
Location star = new Location(60, 20);
while (true)
{
next = snake[0];
Console.Clear();
// show snake
foreach (Location location in snake)
{
Console.SetCursorPosition(location.X, location.Y);
Console.ForegroundColor = ConsoleColor.White;
Console.Write((char) 178);
Console.ResetColor();
}
// show star
Console.SetCursorPosition(star.X, star.Y);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write('*');
Console.ResetColor();
// handle input
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
switch(keyInfo.Key)
{
case ConsoleKey.Escape:
return;
case ConsoleKey.UpArrow:
if (next.Y > 0)
next.Y--;
break;
case ConsoleKey.DownArrow:
if (next.Y < 24)
next.Y++;
break;
case ConsoleKey.LeftArrow:
if (next.X > 0)
next.X--;
break;
case ConsoleKey.RightArrow:
if (next.X < 79)
next.X++;
break;
}
snake.Insert(0, next);
if (next.X == star.X && next.Y == star.Y)
{
Random random = new Random();
star.X = random.Next(0, 80);
star.Y = random.Next(0, 25);
}
else
snake.RemoveAt(snake.Count - 1);
}
}
}
}
Labels:
ascii,
beginner,
c#,
console,
generics,
lists,
random,
sharpdevelop,
snake,
snake ranch,
structs
C# Basics: Snake Game in ASCII Art (Part 1)
Hi! :)
In recent articles, we created a game called Chase the Star (see "C#: ASCII Art Game (Part 1)" and "C#: ASCII Art Game (Part 2)"), and also learned about what The ASCII Table (C#) has to offer to us.
In today's article we're going to create a little game like the classic Snake. The first Snake game I ever played was probably Nibbles, on an ancient DOS machine. Rumour has it that in the Stone Age, when cavemen used to dance around a bonfire and chant "Ugh!", this machine was used to make a series of electronic beeps in a disco fashion. Each caveman would then walk like an Egyptian.
OK, we're going to start with something quite similar to Chase the Star. The difference is that our snake will get longer when it eats the star. Instead of storing just the location of our player, as with Chase the Star, we are going to need a whole list of locations:
Start a new console application. Since we're going to have a lot of locations, it makes sense to define a structure or struct storing X and Y, rather than keeping separate variables for them. This must be declared at the same level as class Program (ideally just before it):
struct Location
{
int X;
int Y;
};
In Main(), we can now store the location of the head of the snake as follows:
Location head;
head.X = 40;
head.Y = 12;
If you try compiling now, you'll get the following error:
'CsSnake.Location.X' is inaccessible due to its protection level (CS0122)
In order to make this work, you need to set the Location's members to public:
struct Location
{
public int X;
public int Y;
};
Even better, instead of setting X and Y separately, we can define a constructor:
struct Location
{
public int X;
public int Y;
public Location(int x, int y)
{
this.X = x;
this.Y = y;
}
};
In Main(), we can now declare head in one line as follows:
Location head = new Location(40, 12);
A constructor, in this case Location(int x, int y), is a method that always has the same name as the struct or class it is in (more about classes another time). It can be used to pass parameters that will be used when the object is created. In many constructors, including this one, those parameters are used to set the member variables in the struct (in this case X and Y).
In this case, we declare a new Location called head and pass 40 and 12 as parameters. Those end up in the constructor, where they are assigned to the X and Y variables within head. The this keyword simply states that we are working with the member variables of head (which is a Location).
We can now add the following at the beginning of Main():
Console.OutputEncoding = System.Text.Encoding.GetEncoding(1252);
Console.Title = "Snake Ranch";
...and some logic to show the head of the snake and handle movement:
while (true)
{
// show head
Console.Clear();
Console.SetCursorPosition(head.X, head.Y);
Console.ForegroundColor = ConsoleColor.White;
Console.Write((char) 178);
Console.ResetColor();
// handle input
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
switch(keyInfo.Key)
{
case ConsoleKey.Escape:
return;
case ConsoleKey.UpArrow:
if (head.Y > 0)
head.Y--;
break;
case ConsoleKey.DownArrow:
if (head.Y < 24)
head.Y++;
break;
case ConsoleKey.LeftArrow:
if (head.X > 0)
head.X--;
break;
case ConsoleKey.RightArrow:
if (head.X < 79)
head.X++;
break;
}
}
We now have something similar to Chase the Star, except that we don't yet have the star functionality:
We are using character number 178 from the ASCII Table to represent the head of the snake. When the user presses an arrow key, we modify the X or Y members of head. We can access them as head.X or head.Y.
Wonderful :) In this article we implemented the first part of our Snake game, and learned to use structs. Tomorrow we will learn to use Lists, which we will need to store each part of the snake. This will allow us to complete the game. :)
Check back tomorrow for more! :)
In recent articles, we created a game called Chase the Star (see "C#: ASCII Art Game (Part 1)" and "C#: ASCII Art Game (Part 2)"), and also learned about what The ASCII Table (C#) has to offer to us.
In today's article we're going to create a little game like the classic Snake. The first Snake game I ever played was probably Nibbles, on an ancient DOS machine. Rumour has it that in the Stone Age, when cavemen used to dance around a bonfire and chant "Ugh!", this machine was used to make a series of electronic beeps in a disco fashion. Each caveman would then walk like an Egyptian.
OK, we're going to start with something quite similar to Chase the Star. The difference is that our snake will get longer when it eats the star. Instead of storing just the location of our player, as with Chase the Star, we are going to need a whole list of locations:
Start a new console application. Since we're going to have a lot of locations, it makes sense to define a structure or struct storing X and Y, rather than keeping separate variables for them. This must be declared at the same level as class Program (ideally just before it):
struct Location
{
int X;
int Y;
};
In Main(), we can now store the location of the head of the snake as follows:
Location head;
head.X = 40;
head.Y = 12;
If you try compiling now, you'll get the following error:
'CsSnake.Location.X' is inaccessible due to its protection level (CS0122)
In order to make this work, you need to set the Location's members to public:
struct Location
{
public int X;
public int Y;
};
Even better, instead of setting X and Y separately, we can define a constructor:
struct Location
{
public int X;
public int Y;
public Location(int x, int y)
{
this.X = x;
this.Y = y;
}
};
In Main(), we can now declare head in one line as follows:
Location head = new Location(40, 12);
A constructor, in this case Location(int x, int y), is a method that always has the same name as the struct or class it is in (more about classes another time). It can be used to pass parameters that will be used when the object is created. In many constructors, including this one, those parameters are used to set the member variables in the struct (in this case X and Y).
In this case, we declare a new Location called head and pass 40 and 12 as parameters. Those end up in the constructor, where they are assigned to the X and Y variables within head. The this keyword simply states that we are working with the member variables of head (which is a Location).
We can now add the following at the beginning of Main():
Console.OutputEncoding = System.Text.Encoding.GetEncoding(1252);
Console.Title = "Snake Ranch";
...and some logic to show the head of the snake and handle movement:
while (true)
{
// show head
Console.Clear();
Console.SetCursorPosition(head.X, head.Y);
Console.ForegroundColor = ConsoleColor.White;
Console.Write((char) 178);
Console.ResetColor();
// handle input
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
switch(keyInfo.Key)
{
case ConsoleKey.Escape:
return;
case ConsoleKey.UpArrow:
if (head.Y > 0)
head.Y--;
break;
case ConsoleKey.DownArrow:
if (head.Y < 24)
head.Y++;
break;
case ConsoleKey.LeftArrow:
if (head.X > 0)
head.X--;
break;
case ConsoleKey.RightArrow:
if (head.X < 79)
head.X++;
break;
}
}
We now have something similar to Chase the Star, except that we don't yet have the star functionality:
We are using character number 178 from the ASCII Table to represent the head of the snake. When the user presses an arrow key, we modify the X or Y members of head. We can access them as head.X or head.Y.
Wonderful :) In this article we implemented the first part of our Snake game, and learned to use structs. Tomorrow we will learn to use Lists, which we will need to store each part of the snake. This will allow us to complete the game. :)
Check back tomorrow for more! :)
Labels:
ascii,
beginner,
c#,
cavemen,
constructors,
snake,
snake ranch,
stone age,
structs,
switch,
this
Subscribe to:
Posts (Atom)



















