Multiplayer Game Programming for Teens with Python: Part 2

I’m sure once in a while, your friends and you go online to play a multiplayer game. In this tutorial, you will learn about multiplayer game programming by creating a simple game. By .

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Taking Turns

Next you need to implement turns so that the players don’t cheat. Believe it or not, you’ve already set up a variable for this (the turn variable). But first, you need a delay function that waits for 10 frames before the game allows the player to place another line.

Add this variable to __init__ in boxes.py:

self.justplaced=10

You want this variable to decrement by 1 each frame, and then reset to 10 when the user places a line. Change the following code in the game:

if pygame.mouse.get_pressed()[0] and not alreadyplaced and not isoutofbounds:
#-----------to-----------#
if pygame.mouse.get_pressed()[0] and not alreadyplaced and not isoutofbounds and self.turn==True and self.justplaced<=0:

This checks to see if it's the player's turn and that they haven't already just placed a line.

Next add this at the top of the update function:

self.justplaced-=1

This decrements the variable by 1 every frame. Now you have to make it so that if a user does place a line, then the counter resets. Add this to inside the if statement that you changed earlier:

self.justplaced=10

Good. One problem you may have noticed is that the turn indicator is always green – and for both clients at the same time! This is only because you haven't hooked up the part of your code that switches the color back and forth.

Find the screen.blit function that draws the green indicator inside the drawHUD() method. Change it to this:

self.screen.blit(self.greenindicator if self.turn else self.redindicator, (130, 395))

Run the game again - one of the indicators should be green and one red.

Boxes10

The indicators are correct, but when you place a line, the game should obviously switch turns. Currently this isn't happening. Let's quickly add that in and then move on to the game logic on the server.

On the server-side, you need it to send the client a message whenever any event happens to tell the client whose turn it is. You can do this by simply adding on to the placeLine method in the Game class on the server. Put this right after the part in the if statement where you update the turn representation:

self.player1.Send({"action":"yourturn", "torf":True if self.turn==1 else False})
self.player0.Send({"action":"yourturn", "torf":True if self.turn==0 else False})

torf stands for “true or false”. This will tell the client whether or not it's their turn. You don't need to send this message when the game is starting up because the client figures out who takes the first turn according to which number player it is.

Let's implement this yourturn command on the client-side. This is fairly easy to do. Add this method to the Game class on the client:

def Network_yourturn(self, data):
    #torf = short for true or false
    self.turn = data["torf"]

Now run the server and two clients. You will have to restart the server since you made a change to it.

Boxes11

As you can see, the clients have to take turns now. Pretty neat, huh?

Now that you've taught your clients how to behave and take turns, you ought to reward them with something for their hard work. And that is - colored boxes!

Programming the Game Logic

This game has simple logic: to determine whether a player has made a square during their turn, the server loops through all of the possible squares and looks for one.

Create a new method in the BoxesServer class called tick(). Add the method as shown:

def tick(self):
    # 1
    index=0
    change=3
    # 2 
    for game in self.games:
        change=3
        for time in range(2):
            # 3
            for y in range(6):
                for x in range(6):
                    # 4
                    if game.boardh[y][x] and game.boardv[y][x] and game.boardh[y+1][x] and game.boardv[y][x+1] and not game.owner[x][y]:
                        if self.games[index].turn==0:
                            self.games[index].owner[x][y]=2
                            game.player1.Send({"action":"win", "x":x, "y":y})
                            game.player0.Send({"action":"lose", "x":x, "y":y})
                            change=1
                        else:
                            self.games[index].owner[x][y]=1
                            game.player0.Send({"action":"win", "x":x, "y":y})
                            game.player1.Send({"action":"lose", "x":x, "y":y})
                            change=0
        # 5
        self.games[index].turn = change if change!=3 else self.games[index].turn
        game.player1.Send({"action":"yourturn", "torf":True if self.games[index].turn==1 else False})
        game.player0.Send({"action":"yourturn", "torf":True if self.games[index].turn==0 else False})
        index+=1
    self.Pump()

Whoa, that's a lot of code! Let's break it down to see what's happening.

  1. At the top of the method, you declare some variables: index, which is commonly used with for loops to keep track of what item you are on, and change, which tells you whether the turn should change and whose turn it should be.
  2. Next you loop through all of the games, resetting the change to 3, which means no change.
  3. Then you loop through all of the possible squares. You do it twice, because it's possible a player could get two squares at once by drawing a middle line between two boxes.
  4. For each possible square, you check to see if there is a square and if so, make sure it wasn't drawn on an earlier turn.
  5. Finally, you check to see who placed the line that made the square and set the change variable correctly.

Now that you have this method, you need to add it to the server. This is easy to do. Go to the bottom of the file and find where it says:

boxesServe.Pump()

Change it to:

boxesServe.tick()

Now that you have some of the game logic on the server, let's add a method on the client to tell it that it either won a square or lost a square (i.e. the other player won a square).

Add these two methods to the client side:

def Network_win(self, data):
    self.owner[data["x"]][data["y"]]="win"
    self.boardh[data["y"]][data["x"]]=True
    self.boardv[data["y"]][data["x"]]=True
    self.boardh[data["y"]+1][data["x"]]=True
    self.boardv[data["y"]][data["x"]+1]=True
    #add one point to my score
    self.me+=1
def Network_lose(self, data):
    self.owner[data["x"]][data["y"]]="lose"
    self.boardh[data["y"]][data["x"]]=True
    self.boardv[data["y"]][data["x"]]=True
    self.boardh[data["y"]+1][data["x"]]=True
    self.boardv[data["y"]][data["x"]+1]=True
    #add one to other players score
    self.otherplayer+=1

These handle the win and lose messages that come over the network. It updates the game state appropriately.

Run your game using the server and two clients, and enjoy taking over the whole board from both sides!

Screen Shot 2013-06-20 at 8.48.01 AM

Game Over, Man!

But wait, when does this game end? You still need to instruct the server to implement the finished() method you added at the end of Part 1. Remember, that method shows either a winning or a losing screen and exits the game.

Add this at the top of the update function:

if self.me+self.otherplayer==36:
    self.didiwin=True if self.me>self.otherplayer else False
    return 1

This checks to see how many squares you have won and how many squares the other player has won. If the total is 36 (the number of squares on the board), the game is over. If the game is over, it checks to see who has the most squares to figure out who won, and returns 1.

Finally, find bg.update() at the very bottom of the file. Replace it with the following:

if bg.update()==1:
    break

At the very end of the file, add this with no indentation:

bg.finished()

Now you should be able to win the game. But before you test it out, let's add one more server and client function. When a client closes the game, you want the other client to close the game, too.

Add this to the ClientChannel class:

def Close(self):
    self._server.close(self.gameid)

Then add this to the BoxesServer class:

def close(self, gameid):
    try:
        game = [a for a in self.games if a.gameid==gameid][0]
        game.player0.Send({"action":"close"})
        game.player1.Send({"action":"close"})
    except:
        pass

To make the client understand the close() command, add this to the client class:

def Network_close(self, data):
    exit()

Run the game again with two clients and play until you win. It shouldn't be too hard. ;)

You are now officially done with the game. If you want to perfect it with music and sounds, continue on to the next section, where you will also give the game network connectivity. Otherwise, skip ahead to the last section to read a little about what you've done and where you can take it from here.