Gameboy in C #

When I just started programming, I think, like many, I wanted to make games. But before me there were a lot of architectural issues that I did not know how to solve, I did not even hear about double buffering, and I wanted to get the result as soon as possible. Therefore, I recently decided to write a project in which it will be possible to write simple games without any problems. Games in this project can be created like GameBoy, that is: tetris, snake, etc. But you can also click on it with the mouse.

Link to the project on GitHub .

In this article I want to make out the creation of a snake.

The first thing you need to start with is to create your own game class and inherit from the Game base class.

class Snake : Game

it already implements the playing field and events that occur when the game transitions from one state to another. Essentially, all we need to do is declare event handling.

public Snake() : base()
{
	OnPreview += BasePreview;
	OnNewGame += Snake_OnNewGame;
	OnUpdateGame += Snake_OnUpdateGame;
	OnGameOver += DrawScore;
}

For OnPreview and OnGameOver events, there are already ready-made stubs in the Game class, you can not implement them. It remains only to initialize a new game and process update events.

private GameBlock head;
private List body;
private GameBlock eat;
private void Snake_OnNewGame()
{
	head = new GameBlock()	{ X = 10, Y = 10, Vector = Vector.Up, Color = GameColor.Green };
	body = new List();
	body.Add( head );
	body.Add( new GameBlock() { X = 10, Y = 11, Vector = Vector.Up, Color = GameColor.Black } );
	body.Add( new GameBlock() { X = 10, Y = 12, Vector = Vector.Up, Color = GameColor.Black } );
	CreateEat();
	DrawField();
}

You can work with it directly to draw a field, or you can use the ready-made GameBlock class, it implements such things as position, direction of movement and color.

In this function, we declared the body of the snake, create the first piece of food and display what is happening on the field.

private void CreateEat()
{
	var emptyBlocks = new List();
	for( int i = 0; i < MainForm.FIELD_SIZE; i++ )
		for( int j = 0; j < MainForm.FIELD_SIZE; j++ )
			if( CheckEmptyBlock( i, j ) )
				emptyBlocks.Add(new GameBlock() { X = i, Y = j, Color = GameColor.Red } );
	if (emptyBlocks.Count > 0)
		eat = emptyBlocks[random.Next( emptyBlocks.Count )];
}

To create a meal, we get a list of empty blocks and with the help of a randomizer (which is already declared in the Game) select random. In case the snake has occupied the entire field, there is a check on the size of the list.

Actually, the function of checking the empty cell:

private bool CheckEmptyBlock(int x, int y) => !( x < 0 || y < 0 || x == MainForm.FIELD_SIZE || y == MainForm.FIELD_SIZE ) && !body.Exists( a => a.Equals( new GameBlock() { X = x, Y = y } ) );

The rendering of the field is as follows:

private void DrawField()
{
	Field.Clear( GameColor.White );
	Field.DrawGameBlock( eat );
	Field.DrawGameBlocks( body );
	WriteScore();
}

As it is not difficult to guess, the field is cleared in white and food with a snake is displayed. WriteScore is another standard feature for displaying a score in a special status bar.

So, we turn to the game update event, which occurs with a frequency of 300 ms.

private void Snake_OnUpdateGame( Controller controller )
{
	ControlMove( controller.GameKey );
	if( CheckGameOver() )
		GameOver();
	else
		SnakeMove();
}

Four things happen in it: changing the direction of movement, checking for the end of the game, calling the event of the end of the game and moving the snake in case everything is in order.

private void ControlMove( GameKey key )
{
	switch( key )
	{
		case GameKey.Left:  head.Vector  = head.Vector == Vector.Right ? Vector.Right : Vector.Left;  break;
		case GameKey.Right: head.Vector  = head.Vector == Vector.Left  ? Vector.Left  : Vector.Right; break;
		case GameKey.Up:    head.Vector  = head.Vector == Vector.Down  ? Vector.Down  : Vector.Up;    break;
		case GameKey.Down:  head.Vector  = head.Vector == Vector.Up    ? Vector.Up    : Vector.Down;  break;
		default: break;
	}
}

To change the direction of movement in the snake we need to change the vector in her head. Therefore, in the control of movement there is a check for the case of inversion of the vector, so that the snake does not start climbing on itself.

private bool CheckGameOver()
{
	switch( head.Vector )
	{
		case Vector.Up: return !CheckEmptyBlock( head.X, head.Y - 1 );
		case Vector.Down: return !CheckEmptyBlock( head.X, head.Y + 1 );
		case Vector.Left: return !CheckEmptyBlock( head.X - 1, head.Y ); 
		case Vector.Right: return !CheckEmptyBlock( head.X + 1, head.Y );
		default: throw new NotImplementedException();
	}
}

To check the end of the game, it is enough to check whether the block in the direction is free or not. As you might guess, the food in the check is ignored.

It remains to parse the snake's movement function:

private void SnakeMove()
{
	var temp = body.Last().Copy();
	foreach( var block in body )
		block.Move();
	for( int i = body.Count - 1; i > 0; i-- )
		body[i].Vector = body[i - 1].Vector;
	if( head.Equals( eat ) )
	{
		score++;
		body.Add( temp );
		CreateEat();
	}
	DrawField();
}

The end of the tail is copied so that if food has been reached, add it as a snake extension. Moving blocks is not difficult, because this function is already implemented in the block class. Then the vectors are distributed over the movement of the snake and checked for intersection with food. If food is found, the bill increments, the snake grows and new food is created. In order for our game to appear in the list of games, you need to add it to the form initialization:

List games = new List();
games.Add( new Snake() );
games.Add( new Tetris() );
games.Add( new Life() );
Application.Run( new MainForm( games ) );

That's all. The entire game code took only 102 lines. As you can see from the example, tetris and game life have already been added to the project. Below you can find the result.

image
Game Selection Menu Game

image
Process

image
End of Game

Also popular now: