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 .
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Multiplayer Game Programming for Teens with Python: Part 2
30 mins
The Game Class
Next you’re going to implement the Game
class, which will represent all of the game elements: a pair of clients, the board map and whose turn it is.
Add this right after the BoxesServer
class in the server.py file:
class Game:
def __init__(self, player0, currentIndex):
# whose turn (1 or 0)
self.turn = 0
#owner map
self.owner=[[False for x in range(6)] for y in range(6)]
# Seven lines in each direction to make a six by six grid.
self.boardh = [[False for x in range(6)] for y in range(7)]
self.boardv = [[False for x in range(7)] for y in range(6)]
#initialize the players including the one who started the game
self.player0=player0
self.player1=None
#gameid of game
self.gameid=currentIndex
This class represents the state of the game. The server will be the “official manager” of the state of the game, and will keep each client updated about what to display.
When the first client connects, the server should create a new game. The server will then have a list of games and queue of waiting players so that when another client connects, the server knows whether create a new game for them or have them join a player who is waiting. Let’s add that functionality now.
Add this method to the beginning of the BoxesServer
class:
def __init__(self, *args, **kwargs):
PodSixNet.Server.Server.__init__(self, *args, **kwargs)
self.games = []
self.queue = None
self.currentIndex=0
See that crazy line in there that contains a bunch of confusing things that starts with PodSixNet
? Since you are extending something, you still have it call the init
of the class that you are extending. So this calls the PodSixNet server class’s initializer, passing all the arguments through.
The currentIndex
variable keeps track of the existing games, incrementing one for every game created.
Let’s now add the part of code that puts a client into a queue or has them join a game. Add this to the end of Connected()
:
if self.queue==None:
self.currentIndex+=1
channel.gameid=self.currentIndex
self.queue=Game(channel, self.currentIndex)
else:
channel.gameid=self.currentIndex
self.queue.player1=channel
self.queue.player0.Send({"action": "startgame","player":0, "gameid": self.queue.gameid})
self.queue.player1.Send({"action": "startgame","player":1, "gameid": self.queue.gameid})
self.games.append(self.queue)
self.queue=None
As you can see, the server checks if there is a game in the queue. If there isn’t, then the server creates a new game and puts it in the queue so that the next time a client connects, they are assigned to that game. Otherwise, it sends a “start game” message to both players.
Consider this: When a client places a line on the board, the server knows where they placed it on the grid. But – assuming there’s more than one game – the server doesn’t know which game the client is in. Thus, the server doesn’t know which map representation to update and which other client to inform that the map has changed.
To get this information to the server, you first need to send the client the gameid
when they are assigned to a game. You can see that this is passed in the “startgame” message as one of the parameters. This will also be your notification to the client that the game has started.
You also need to make the client wait for that and when it gets the message, you need to figure out whose turn it is as well. This will tell both of the players that the game has started and that they are a specific gameid
. Let’s do that next.
Add this to the class on the client side:
def Network_startgame(self, data):
self.running=True
self.num=data["player"]
self.gameid=data["gameid"]
You want the client to wait until it receives the message to start the game. Add this to the end of __init__
:
self.running=False
while not self.running:
self.Pump()
connection.Pump()
sleep(0.01)
#determine attributes from player #
if self.num==0:
self.turn=True
self.marker = self.greenplayer
self.othermarker = self.blueplayer
else:
self.turn=False
self.marker=self.blueplayer
self.othermarker = self.greenplayer
Two Players, One Game
Remember that function that I said wouldn’t work yet that draws the boxes a certain color (drawOwnermap
)? Well, now it will, since the client now knows if you are blue or green on the board. Add this right after the call to self.drawHUD()
in update
:
self.drawOwnermap()
Run the game now. You’ll need three Terminal windows this time – one to run the server, and two to run the clients, because the game won’t start unless there are two players. Not much works yet, but at least the two games are connected to the same server!
Let’s do a quick implementation of placing a line. First you need to add a method to the Game
class in the server file for when a client wants to place a line. The Game
class will then check if it is that client’s turn and if it is, add the piece to the map representations of both clients.
Add this method to the Game
class in the server file:
def placeLine(self, is_h, x, y, data, num):
#make sure it's their turn
if num==self.turn:
self.turn = 0 if self.turn else 1
#place line in game
if is_h:
self.boardh[y][x] = True
else:
self.boardv[y][x] = True
#send data and turn data to each player
self.player0.Send(data)
self.player1.Send(data)
This code checks if the move is valid and if it is, sends it off to both clients and updates the representations of the game board and the turn.
Next you need to enable the server to call this method. Add this to BoxesServer
in server.py:
def placeLine(self, is_h, x, y, data, gameid, num):
game = [a for a in self.games if a.gameid==gameid]
if len(game)==1:
game[0].placeLine(is_h, x, y, data, num)
This code loops through all of the games and finds the one with the same gameid
as the client. Then it relays the information to the game by calling Game.placeline()
.
You have one last method to add to the ClientChannel
class in the server file. Add this method to the ClientChannel
class in server.py:
def Network_place(self, data):
#deconsolidate all of the data from the dictionary
#horizontal or vertical?
hv = data["is_horizontal"]
#x of placed line
x = data["x"]
#y of placed line
y = data["y"]
#player number (1 or 0)
num=data["num"]
#id of game given by server at start of game
self.gameid = data["gameid"]
#tells server to place line
self._server.placeLine(hv, x, y, data, self.gameid, num)
This reads the message that you’ve been seeing the server print out when the player places a line. It pulls out each argument, and calls the placeLine method on the server.
Now you have your game so that the server relays information to the client. There is one big problem, though: The client doesn’t know what to do with this information. Let’s add another method to the client file to fix this problem.
Add this to the client file:
def Network_place(self, data):
#get attributes
x = data["x"]
y = data["y"]
hv = data["is_horizontal"]
#horizontal or vertical
if hv:
self.boardh[y][x]=True
else:
self.boardv[y][x]=True
This is called when the client receives a place line message from the server. It reads out the parameters and updates the game’s state as appropriate.
Now try running the program. The first line you place will show up on the other side (but further lines won’t work – you’ll get to that soon).
You have just implemented your first multiplayer server! This was not easy to do, as you can attest. Look back at all of the things you’ve done to get here.