Page 1 of 1

Computer Networking, Two-Player Interactive Game

#1 DK3250   User is offline

  • Pythonian
  • member icon

Reputation: 437
  • View blog
  • Posts: 1,410
  • Joined: 27-December 13

Posted 28 March 2017 - 07:40 AM

Computer Networking, Two-Player Interactive Game

Disclaimer
It is only fair to admit that Python Network Programming is not my core competence. Some code lines are still a bit unclear to me.
What I present here is very simple and probably not an optimal code. But it works. I'll be happy to get feedback and suggestions improving the performance.

Background
With this tutorial, I move into an area where I am still a novice. I have, however, spend quite long time figuring out how to connect two computers for interactive gaming. My code works and there is no other tutorial that covers the subject.

So, bear with me if something is not best practice – and do come forward with suggestions for improvements.

The code is the simplest I can think of; two icons move around on the screen. They do nothing and do not interact. But they are controlled from two different computers – and the game play is up to you.

Nevertheless, this tutorial will be fairly large. We need to cover DHCP addresses and IP addresses, Router settings, the socket module and finally the interactive game.

DHCP address
DHCP is acronym for Dynamic Host Configuration Protocol. Your DHCP address is the unique number used in communication to your Router, something like ”192.168.0.18” or “10.0.0.8”. To find the DHCP address, open a command prompt and type 'ipconfig', you'll get an output like:

Posted Image

The address is here called Ipv4 Address. I normally use the term 'DHCP address' for addresses inside the Router, and 'IP address' for the Router access from the Internet. Often, both are just called 'IP'.

If you plan to connect to another computer on the same Router e.g. A family member in the same house, you should also look for the DHCP address of this other computer. More to read: http://whatismyipaddress.com/dhcp

Ports
You will also need a port number for the communication. Ports are address inside the computer used when communicating. This is a whole major subject in its own right; what you need to know is that ports used here should always be in the range 1025-65535. I tend to use 9990-9999.

IP addresses
If you plan to connect to a computer on another network, you need to know the two Router IP addresses involved. This is the IP address of your Router when seen from outside the local network. The easiest way to get the IP address is by using one of the many free services on the Internet, such as http://whatismyipaddress.com/

NOTE:
If your Router has dynamic IP-address, the value may change.
You will need to check the IP address periodically, especially if the Router has been stopped.

Port forwarding
If you plan to connect two networks, you’ll need to set-up the port forwarding in both Routers.
This can be a bit of a challenge, and depends of your Router. Please look-up your Router manual for details.
A signal arriving to the Router will be blocked by your firewall unless port forwarding is established. With port forwarding, the Router knows which DHCP address that listen to each port; thus, directing the signal to the correct device.
You will need to assign port forwarding for the port number chosen in the program (see below); in the Router you assign the port to your computer; remember to enable the connection after setting it in the Router.

If you run the two computers on the same LAN, you don't need to consider port forwarding.

Communication
To communicate between two computers, we use the Python socket module.

We instantiate a small Server and let it 'listen' for data traffic on the specified port. Any data on the port will be translated to meaningful instructions using a small home build 'protocol' – as you will see, the protocol used here is extremely simple.

All the Server does is listen on the port and provide data to our program.

Whenever we want to send data; we first construct the message using the protocol and we then enter a 'client cycle':

1. initialize a Client
2. send the message
3. close the Client

So, in this program clients are only for sending, and clients only 'live' to send one single message.

Code structure
The code I'll show consist of two programs. The first one is a module used by the second one. In this module I have placed all the code necessary for communication. The main program is (almost) standard Python/Pygame code.
You need to download the two programs in the same folder; the first code MUST be named 'connection.py'. I'll refer to the second program as 'game'.
The two programs must be placed on two computers. The DHCP/IP addresses of the two computes must be hard coded to line 15 and 17 in both versions of 'game'. Also, make sure that the ports are mirrored in line 16 and 18 (or use same port on both computers).

connection.py

import socket, sys

data_payload = 2048
backlog = 2

class Client():
    def __init__(self, ip, port):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(20)
        try:
            self.sock.connect((ip, port))
        except socket.timeout:
            print('Timeout at connect')

    def send(self, message):
        try:
            self.sock.sendall(message.encode())
        except socket.timeout:
            print('Timeout at send')

    def shutdown(self):
        self.sock.close()

class Server():
    def __init__(self, ip, port):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((ip, port))
        self.sock.listen(backlog)

    def receive(self):
        data = None
        self.client, address = self.sock.accept()
        data = self.client.recv(data_payload).decode()
        return data

    def shutdown(self):
        self.sock.close()

def send(msg, OTHER_HOST, OTHER_PORT):
    client = Client(OTHER_HOST, OTHER_PORT)
    client.send(msg)
    client.shutdown()



The code here I've collected and assembled by a lot of reading and testing; I'm not sure of all details, so bear with me for not explaining it in detail.
On a high-level the code describes two class objects, Client and Server with their 'send' and 'receive' methods respectively. Furthermore a small function 'def send():' executing the client cycle described above.
The two print statements in line 13 and 19 are remnants from the code development. In a real game the timeouts in communication should be handled more gracefully; this is still work in progress.

The code utilizes the socket module.
Link to the socket documentation: https://docs.python....ary/socket.html
Link to socket tutorial: https://docs.python....to/sockets.html

game

import pygame, sys, socket
import connection
pygame.init()

""" This demonstrates a two-player game to be played from two different PCs
    connected to a local network (LAN) or even via the Internet. The code
    requeres access to the module 'connection' for handling of generel server
    and client code.

    Data send between the two players must be 'string'-format.
"""

#####################################################################
## --- NEXT 4 LINES MUST BE MODIFIED TO MATCH ACTUAL SITUATION --- ##
MY_SERVER_HOST = '192.168.1.59'
MY_SERVER_PORT = 9999
OTHER_HOST = '192.168.1.18'
OTHER_PORT = 9992
#####################################################################

X = 600
Y = 400

screen = pygame.display.set_mode((X, Y))

WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)


class Player():
    def __init__(self, pos):
        self.img = pygame.surface.Surface((20, 20))
        self.rect = self.img.get_rect(center = pos)

    def move(self, dir):
        if dir == 'up' and self.rect.top > 0:
            self.rect.centery -= 2
        if dir == 'down' and self.rect.bottom < Y:
            self.rect.centery += 2
        if dir == 'left' and self.rect.left > 0:
            self.rect.centerx -= 2
        if dir == 'right' and self.rect.right < X:
            self.rect.centerx += 2

    def draw(self):
        screen.blit(self.img, self.rect)

    def make_data_package(self):
        datax = str(self.rect.centerx).rjust(4, '0')
        datay = str(self.rect.centery).rjust(4, '0')
        return datax + datay


class Player_1(Player):
    def __init__(self, pos=(200, 200)):
        super().__init__(pos)
        self.img.fill(RED)


class Player_2(Player):
    def __init__(self, pos=(400, 200)):
        super().__init__(pos)
        self.img.fill(BLUE)


def ip_value(ip):
    """ ip_value returns ip-string as integer """
    return int(''.join([x.rjust(3, '0') for x in ip.split('.')]))


def define_players():
    if ip_value(MY_SERVER_HOST) > ip_value(OTHER_HOST):
        me = Player_1()
        enemy = Player_2()
    else:
        me = Player_2()
        enemy = Player_1()
    return me, enemy


def event_handling():
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            server.shutdown()
            pygame.quit()
            sys.exit()


def get_input():
    keys = pygame.key.get_pressed()
    for button, direction in [(pygame.K_UP, 'up'), (pygame.K_DOWN, 'down'),
                              (pygame.K_LEFT, 'left'), (pygame.K_RIGHT, 'right')]:
        if keys[button]:
            me.move(direction)


def data_transfer():
    me_data = me.make_data_package()
    connection.send(me_data, OTHER_HOST, OTHER_PORT) # the send code

    enemy_data = server.receive() # the receive code
    
    enemy.rect.centerx = int(enemy_data[:4])
    enemy.rect.centery = int(enemy_data[4:])


def update_screen():
    screen.fill(WHITE)
    enemy.draw()
    me.draw()
    pygame.display.flip()
    pygame.time.wait(50)


me, enemy = define_players()
server = connection.Server(MY_SERVER_HOST, MY_SERVER_PORT)        
            
while True:
    update_screen()
    event_handling()
    get_input()
    data_transfer()



The idea in this code is that both players use exactly same code (apart from the IP and port addresses). The two player objects needed are called 'me' and 'enemy'. The code ensures that the 'me' player on one computer is perceived as the 'enemy' player on the other, and vise versa.

In line 1-2 imports are made; the module 'socket' is from the standard library, it provides all the server and client code. The module 'connection' is the code provided in this tutorial, above.

Line 15-18 is where you need to insert the IP and port addresses relevant for you. I could have wrapped this in a nice input page, but I want to keep this tutorial as simple as possible.

When you are connected to a LAN, it is possible to get the local IP address using:

MY_SERVER_HOST = socket.gethostbyname(socket.gethostname())


But as you still need to enter OTHER_HOST and the port numbers, I have kept things as transparent as possible.

The class Player() is a general player object. It has three small methods; move(), draw() and make_data_package().

The make_data_package() executes the 'protocol' for data transfer.
We want to send the x- and y- position of the player; the two values are converted to string of length 4, padded with zeros as necessary. Examples: 2 → '0002'; 123 → '0123'.
Finally, the two strings are concatenated to a structure like 'xxxxyyyy', which it the data package to send.
If we jump to line 102-105, this is where data are received and unpacked; you see how we convert the string back to two integers, updating data for the other player.

The two player objects, Player_1 and Player_2, inherits from Player. The two instances only differ in starting position and color.

The small ip_value() function converts an IP address to an integer; it does so by padding each octet (three digit group) with zeros and removing the dots. Example: '192.168.0.18' → 192168000018.

As the two DHCP/IP addresses will always be different, the comparison in line 73 will always turn out 'True' on one computer and 'False' on the other. We don't care which is which, but we use this to ensure that 'me' on one computer is Player_1 and 'me' on the other is Player_2.
In this small code, it is of less importance, it only ensures two different start positions, but in more complicated games you may need to define different roles like 'master' and 'slave'.

The event_handling() function is standard, just notice the server.shutdown() included in the termination of the program.

The get_input() is also simple. Could also be made with a dictionary, but I chose a small list of tuples.

In the data_transfer() function the data package is created and send to the other computer.
Then we listen for received data which is then converted as explained above.

The update_screen() needs no comments.

Finally, in line 116-117, the two Player instances and the Server instance are created.
The small game loop in line 119-123 is simple.

Comments
I have tried to keep everything as simple as possible, only focusing on code related to the data transfer and data handling.

Posted Image

The 'game' is not a game, but rather a framework on which a game can be build. I plan to issue a tutorial demonstrating a full interactive game building on the code shown here; but you don't need to wait for it – you can probably make something yourself now.

Play around and feel free to use this in any way you can think of.
If you find improvements, I’ll be happy to learn about it as a respond to this tutorial.

Github
I have placed the code shown in this tutorial on github: https://github.com/D...omputer-network
This is an invitation to all interested to participate in further development of this code.

Is This A Good Question/Topic? 0
  • +

Replies To: Computer Networking, Two-Player Interactive Game

#2 pm-c   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 6
  • Joined: 12-March 18

Posted 17 March 2018 - 08:34 AM

Thank you very much for your code. I have been looking quite som time for a solution like this: short, clear and simple.
I teach kids above the age of 11 in python and needed something like this for smal games.
pm-c
Was This Post Helpful? 0
  • +
  • -

#3 DK3250   User is offline

  • Pythonian
  • member icon

Reputation: 437
  • View blog
  • Posts: 1,410
  • Joined: 27-December 13

Posted 17 March 2018 - 09:09 AM

I'm glad that you liked the tutorial.
I've trained children in Python myself in many years.
This is not beginners stuff, but it is amazing how good some of those kid gets...

I can see you are quite new here on DIC - welcome.
Was This Post Helpful? 0
  • +
  • -

#4 pm-c   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 6
  • Joined: 12-March 18

Posted 25 March 2018 - 06:39 AM

View PostDK3250, on 17 March 2018 - 09:09 AM, said:

I'm glad that you liked the tutorial.
I've trained children in Python myself in many years.
This is not beginners stuff, but it is amazing how good some of those kid gets...

I can see you are quite new here on DIC - welcome.


Any new versions in the pipeline?
Was This Post Helpful? 0
  • +
  • -

#5 DK3250   User is offline

  • Pythonian
  • member icon

Reputation: 437
  • View blog
  • Posts: 1,410
  • Joined: 27-December 13

Posted 25 March 2018 - 07:06 AM

I was waiting for feedback (such as your post) before I'll continue the tutorial series.

I did plan to issue a full two-player version of the snake game.
This will neatly combine two existing tutorials.
But maybe not introduce much new stuff.

Or, did you have something special in mind?
Was This Post Helpful? 0
  • +
  • -

#6 pm-c   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 6
  • Joined: 12-March 18

Posted 26 March 2018 - 03:11 AM

I have been working on two small games for kids, one is a clone of Pong, the other a clone of Tic-tac-toe, both based on Pygame. My intention is to extend the games to be played as two player games in a LAN, using a modification of your code.
Was This Post Helpful? 0
  • +
  • -

#7 DK3250   User is offline

  • Pythonian
  • member icon

Reputation: 437
  • View blog
  • Posts: 1,410
  • Joined: 27-December 13

Posted 26 March 2018 - 06:00 AM

Sounds interesting!
Tic-Tac-Toe should be quite straightforward to implement as the 'action' is limited.

In Pong, you'll need to send both ball position and player position and handle the graphics.
A bit more complicated but definitely manageable.
I did a similar thing for my Snake Game.

Both are probably an ambitious level for kids; but with proper introduction and explanations you might succeed.

You started (in post #4) to ask for a possible new version.
What do feel is missing or could be improved?

If you need help on this feel free to ask; either here in the tutorial for questions directly related to the tutorial code, or in the general python forum for all other questions i.e. pygame, game logic, etc.
Was This Post Helpful? 0
  • +
  • -

#8 pm-c   User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 6
  • Joined: 12-March 18

Posted 26 March 2018 - 09:42 AM

For the moment I am not capable of improving your code in any way. I try to get a better understanding of socket programming in general by reading www.bogotobogo.com/python/python_network_programming_server_client.php. If you know better places for socket programming tutorials, please let me know.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1