2
\$\begingroup\$

I've made a 2 player game in c# console that is a ping pong game. I've made it in more or less 4 hours and in my opinion it is the best performing game I've made so far.

Just to let you know, I am a new programmer and I've started with C# coding, as I believe it's a good beginner language.

I've made a few C# console projects before, and one of them were a snake game.

I hope you don't go to harsh on me, but I would love to know all of the improvements I could learn to make this and future games better!

For now I am sticking to Console and in the future will dip toes into GUI coding

Here's the code (Made with Visual studio 2022)

Console.CursorVisible = false;
int playerpos = 0;
int player2pos = 0;
int prevpos = 1;

GameLoop(playerpos, player2pos, prevpos);

static void GameLoop(int playerpos, int player2pos, int prevpos)
{
    Console.Write("When youre ready press any key!");
    Console.ReadKey(); // when you ready press any key
    ConsoleKeyInfo cki = new ConsoleKeyInfo();
    string gamestart = "";
    while (gamestart != "N")
    {
        int score = 0;
        int lenght = 50 * 2;
        int height = 20 * 2;
        playerpos = height / 2;
        player2pos = height / 2;
        int[] ballpos = new int[] { lenght, height / 2, 1, 1 };
        Board(height, lenght);
        int who = 0;
        bool game = true;
        while (game != false)
        {
            do
            {
                while (Console.KeyAvailable == false)
                {
                    Thread.Sleep(20);
                    score = Score(height, lenght, score, playerpos, ballpos);
                    Player(playerpos, height, lenght, player2pos, who);
                    ballpos = Ball(lenght, height, ballpos, playerpos, player2pos);
                    if (ballpos[3] == 5 || ballpos[3] == 6)
                    {
                        game = false;
                        break;
                    }
                }
                if (ballpos[3] == 5 || ballpos[3] == 6)
                    break;
                cki = Console.ReadKey(true);
                if (cki.Key == ConsoleKey.W || cki.Key == ConsoleKey.S)
                {
                    who = 1;
                    playerpos = Move(cki, playerpos, height, player2pos);
                }
                if (cki.Key == ConsoleKey.UpArrow || cki.Key == ConsoleKey.DownArrow)
                {
                    who = 2;
                    player2pos = Move(cki, playerpos, height, player2pos);
                }
            } while (cki.Key != ConsoleKey.W || cki.Key != ConsoleKey.S || ballpos[3] != 5 || ballpos[3] != 6);
        }
        Console.Clear();
        if (ballpos[3] == 5)
            Console.WriteLine("Player 2 Won the game! With a score of = " + score);
        if (ballpos[3] == 6)
            Console.WriteLine("Player 1 Won the game!");
        Console.WriteLine("\nDo you want to end the game? N = exit, anything else continue");
        gamestart = Console.ReadLine().ToUpper();
    }
}

static void Board(int height, int lenght)
{
    Console.SetCursorPosition(0, 0);
    for (int i = 0; i < height; i++){
        Console.CursorVisible = false;
        for (int j = 0; j < lenght; j++){
            if(j == lenght - 1)
            {
                Console.Write("# \n");
                continue;
            }
            if(i == 0 || i == height-1)
            {
                Console.Write("##");
                continue;
            }
            if((j == 0) && (i != 0 || i != height-1))
            {
                Console.Write("# ");
                continue;
            }
            if(j == lenght/2 && i != 0 && i != height-1)
            {
                Console.Write("| ");
                continue;
            }
            if( i != 0 || i != height-1 || j != 0 ||j != lenght - 1)
            {
                Console.Write("  ");
                continue;
            }
        }
    }
}

static int Score(int height, int lenght, int score, int p1, int[] ballpos)
{
    if ((ballpos[1] == p1 - 2 || ballpos[1] == p1 - 1 || ballpos[1] == p1 || ballpos[1] == p1 + 1 || ballpos[1] == p1 + 2) && (ballpos[3] == 4 && ballpos[0] == 4 || ballpos[3] == 2 && ballpos[0] == 4) && ballpos[3] != 5)
        score++;
    string scr = "Score of this game is:";
    Console.CursorVisible = false;
    Console.SetCursorPosition(lenght - scr.Length/2, height );
    Console.Write(scr);
    Console.SetCursorPosition( lenght - (scr.Length / 2), height + 2);
    Console.Write(score);
    return score;
}

static void Player(int playerpos, int height, int lenght, int player2pos, int who)
{
    if (who == 1)
    {
        for (int i = 1; i < height - 1; i++)
        {
            Console.SetCursorPosition(4, i);
            Console.Write(" ");
        }
        for (int i = -2; i < 3; i++)
        {
            if (playerpos + i == 0)
                return;
            Console.SetCursorPosition(4, playerpos + i);
            Console.Write("█");
        }
    }
    else if (who == 2)
    {
        for (int i = 1; i < height - 1; i++)
        {
            Console.SetCursorPosition(lenght * 2 - 6, i);
            Console.Write(" ");
        }
        for (int i = -2; i < 3; i++)
        {
            if (player2pos + i == 0)
                return;
            Console.SetCursorPosition(lenght * 2 - 6, player2pos + i);
            Console.Write("█");
        }
    }
}

static int Move(ConsoleKeyInfo cki, int playerpos, int height, int player2pos)
{
    switch (cki.Key)
    {
        case ConsoleKey.W:
            if (playerpos == 3)
                return playerpos;
            playerpos--;
            return playerpos;
        case ConsoleKey.S:
            if (playerpos == height - 4)
                return playerpos;
            playerpos++;
            return playerpos;
        case ConsoleKey.UpArrow:
            if (player2pos == 3)
                return player2pos;
            player2pos--;
            return player2pos;
        case ConsoleKey.DownArrow:
            if (player2pos == height - 4)
                return player2pos;
            player2pos++;
            return player2pos;
        default:
            return playerpos;
    }
}

static int[] Ball(int lenght, int height, int[] ballpos, int playerpos, int player2pos)
{

    Console.SetCursorPosition(ballpos[0], ballpos[1]);
    Console.Write(" ");

    if(ballpos[0] == lenght)
    {
        for (int i = 1; i < height-1; i++)
        {
            Console.SetCursorPosition(lenght, i);
            Console.Write("|");
        }
    }
    if (ballpos[1] == height - 2 && ballpos[3] == 3) // From Down -> To Up ->
    {
        ballpos[3] = 1;
        ballpos[2] = 1;
    }
    if (ballpos[1] == height - 2 && ballpos[3] == 4) // From Down <- To Up <-
    {
        ballpos[3] = 2;
        ballpos[2] = 3;
    }
    if (ballpos[1] == 1 && ballpos[3] == 1) // From Up -> To Down ->
    {
        ballpos[3] = 3;
        ballpos[2] = 2;
    }
    if (ballpos[1] == 1 && ballpos[3] == 2) // From Up <- To Down <-
    {
        ballpos[3] = 4;
        ballpos[2] = 4;
    }
    if ((ballpos[1] == player2pos - 2 || ballpos[1] == player2pos - 1 || ballpos[1] == player2pos || ballpos[1] == player2pos + 1 || ballpos[1] == player2pos + 2) && ballpos[3] == 3 && ballpos[0] == lenght*2 - 6 ||
        (ballpos[1] == player2pos - 2 || ballpos[1] == player2pos - 1 || ballpos[1] == player2pos || ballpos[1] == player2pos + 1 || ballpos[1] == player2pos + 2) && ballpos[3] == 1 && ballpos[0] == lenght*2 - 6) // From Up -> To Up <-
    {
        if (ballpos[3] == 1)
        {
            ballpos[3] = 2;
            ballpos[2] = 3;
        }
        else
        {
            ballpos[3] = 4;
            ballpos[2] = 4;
        }
    }
    if (ballpos[0] == lenght * 2 - 2 && ballpos[3] == 1 || ballpos[0] == lenght * 2 - 2 && ballpos[3] == 3) // From Up -> To Up <-
    {
        ballpos[3] = 6;
        return ballpos;
    }
    if ((ballpos[1] == playerpos - 2 || ballpos[1] == playerpos - 1 || ballpos[1] == playerpos || ballpos[1] == playerpos + 1 || ballpos[1] == playerpos + 2) && ballpos[3] == 4 && ballpos[0] == 4 ||
        (ballpos[1] == playerpos - 2 || ballpos[1] == playerpos - 1 || ballpos[1] == playerpos || ballpos[1] == playerpos + 1 || ballpos[1] == playerpos + 2) && ballpos[3] == 2 && ballpos[0] == 4) // From Player 1 Succsess
    {
        if (ballpos[3] == 4){
            ballpos[3] = 3;
            ballpos[2] = 2; }
        else{
            ballpos[3] = 1;
            ballpos[2] = 1;}
    }
    if(ballpos[0] == 0 && ballpos[3] == 2 || ballpos[0] == 0 && ballpos[3] == 4) // Player 1 fail
    {
        ballpos[3] = 5;
        return ballpos;
    }

    if (ballpos[2] == 1) // Up ->
    {
        ballpos[0] += 2;
        ballpos[1]--;
    }
    if (ballpos[2] == 2) // Down ->
    {
        ballpos[0] += 2;
        ballpos[1]++;
    }
    if (ballpos[2] == 3) // Up <-
    {
        ballpos[0] -= 2;
        ballpos[1]--;
    }
    if (ballpos[2] == 4) // Down <-
    {
        ballpos[0] -=2 ;
        ballpos[1]++;
    }
     Console.SetCursorPosition(ballpos[0], ballpos[1]);
    Console.Write("0");

    return ballpos;
}
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Structure

A first step in improving the structure of the code for extensibility would be to define what the game actually is. If we consider the scenario where you will implement this game in a GUI as well, it would be desirable to split the functionality so that the game remains the same and only the input and output need to be changed. A first step would be to rename GameLoop to RunConsolePongGame. As the "game loop" typically is the while loop that handles input, game-logic, and drawing of graphics. In your case, the first while loop inside GameLoop.

A second step would be to split all the logic into these three functions and when you would implement the GUI version in the future you could re-use the game-logic function straight up!

Abstraction

Reasons to abstract functionality (break out chunks of code into its own function under a name) is for readability and re-useability.

Player

"Player" would conventionally refer to some sort of class implementation, an object. As a rule of thumb; a function that has a side effect (such as drawing to a screen) and does not return a value, should always have a verb in it. DrawPlayer, FireRockets etc. It is something that is performed, a Player is just something that exists.

Ball

This function does two things, it draws to the console and returns a new position for the ball. This should be split into two separate implementations: DrawBall which does not return anything, and something like newBallPosition, gameTick, or newGameState, that advances the game according to the game logic.

newBallPosition

Would take a ball position, player positions and return a new position.

gameTick or newGameState

Would take player input as well as player positions and ball position and return new player positions and a new ball position.

Player input

This should be put in its own function and return the actions the players have taken, e.g. getPlayerInput.

Example

This is pseudocode just to show an example of the structure, it is not valid code in any language.

class GameState:
  running, ball,
  playerPosition1, playerPosition2

class PlayerInput:
  player1Up, player1Down
  player2Up, player2Down
  playerQuit

def runConsolePongGame:
  gameState = new GameState
  while gameState.running
    payerInput = getPlayerInput()
    gameState = gameTick(playerInput, gameState)
    drawGame(gameState)

As per C# style the whole game might fit better into a Game class instead which holds these functions, but I will not comment further on that and take especially the naming with a grain of salt, I am not too familiar with C# and C# conventions.

Data, Objects, Classes

Right now you only use primitive values in variables and arguments, such as string, int, int[], and bool. It is desirable to "clump" together variables that belong together in the same context under a specified name, such as the GameState in my example and instead of an int[] array representing the ball position you could define the ball as a class:

class Ball {
  int x;
  int y;
  Color color;
}

(And color here itself refers to another class which contains color data)

And you could pass an object of type Ball around instead:

Ball ball = new Ball()

This makes it easy to add properties (such as color) to this object that can be used everywhere the ball is referenced.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.