Piader – simple game – part 3

Today we will add player and it’s controls.

Controls, that’s something we will do a different way, we have no keyboard attached, no joystick and no pad. So what now ? Lets write a control server, a threaded class that will listen for control signals on some port. This way we will be able to use anything to move a player.

As first input we will use host keyboard, small class that will read keys and send them to game.

Download source code

Player

But first things first. We need to write player class. Lets make player sprite directional, it will look different when moving left and right. Front of player is a cabin and back a missile launcher.

So we need to keep information about heading because start point for missile is depending on it.

Create player.py and copy:

import piader.item as item

class Player(item.Item):
    """Player"""
    def __init__(self, x, y, max_x, objects):
        """init enemy"""
        self.pos_x = x
        self.pos_y = y
        self.max_x = max_x
        self.sprite = {
            'left': "&#",
            'right': "#&"
        }
        self.heading = 'left'
        self.missile_current = 0
        self.missile_max = 1
        self.objects = objects

    def tick(self):
        """action in tick"""
        pass

    def get_position(self):
        """enemy position"""
        return self.pos_x, self.pos_y

    def get_sprite(self):
        """get sprite"""
        return self.sprite[self.heading]

    def event_discard(self):
        """discard enemy - not implemented yet"""
        pass

Remember where we initialized Enemy class ? It was in game.py find it and under it add

self.objects.append(player.Player(9, 5, 16, self.objects))

Don’t forget about imports !

When you run game you will see new sprite on screen.

Moving Player

Now lets put player in motion. We won’t do this the simplest way but also not hardest 🙂

Small server will receive packets, parse then into events and store them in queue. In next game tick queue will be read and all events parsed.  Who know what events we will be adding later.

Just to make it more strange we will allow multi-connections, so we may have more than one client that will be sending events.

Create file event_server.py. We will put two classes there. One is EventServerThread class, its responsible for listening and when connection is made, instance of  second class ListenerThread will be created. There will be connection handling, reading a key and storing it in queue.

Queue is used to connect all threads together. Lets see the code:

import socket
from threading import Thread

TCP_IP = '0.0.0.0'
TCP_PORT = 5005
BUFFER_SIZE = 1024


class ListenerThread(Thread):
    """connection listener"""
    def __init__(self, conn, addr, queue):
        Thread.__init__(self)
        self.client = conn
        self.addr = addr
        self.work = True
        self.queue = queue

    def run(self):
        """thread"""
        while self.work:
            data = self.client.recv(BUFFER_SIZE)
            if not data:
                break
            else:
                self.queue.put(data)

        self.client.close()

    def join(self, timeout=None):
        """stop thread"""
        self.work = False
        self.client.close()
        Thread.join(self, timeout)


class EventServerThread(Thread):
    """server thread"""
    def __init__(self, queue):
        Thread.__init__(self)
        self.work = True
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.settimeout(0.5)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind((TCP_IP,TCP_PORT))
        self.socket.listen(3)
        self.threads = []
        self.queue = queue

    def run(self):
        """start server thread"""
        try:
            print "Server is listening for connections..."
            while self.work:
                client, address = self.socket.accept()
                listener = ListenerThread(client,address, self.queue)
                listener.start()
                self.threads.append(listener)
        finally:
            self.socket.close()
        for t in self.threads:
            t.join()
        print "Server down"

    def join(self, timeout=None):
        """stop server and all listeners"""
        self.work = False
        self.socket.close()
        Thread.join(self, timeout)

This is not the best code but sufficient for our needs. Our goal is to upgrade lcd class not game. Game is just a tool. But we need this tool to work 🙂

Now open game.py and change all init to this:

    def __init__(self, lcds):
        "init class"
        self.lcds = lcds
        self.player = player.Player(9, 5, 16, self.objects)
        self.queue = Queue.Queue()
        self.event_server = event_server.EventServerThread(self.queue)
        self.init_lcds()
        self.game_on = True
        self.init_game()

    def init_lcds(self):
        "inits lcds"
        for lcd in self.lcds:
            lcd.init()

    def init_game(self):
        self.objects.append(enemy.Enemy(2, 0, self.width, self.objects))
        self.objects.append(self.player)
        self.event_server.start()

Lets quickly see what is going on. Thread EventServerThread is started in game. It receive Queue class for thread communication. When connection arrive its starting another thread to use this connection. See that queue is available everywhere, so we can have multiple connections to control game.

We need to modify tick function in game.py at the beginning add:

        try:
            key = self.queue.get(True, 0.05)
            print "tick: ", repr(key)
        except Queue.Empty:
            pass

This will get data from queue and print it in console, thats for now.

We are missing one more important part. A client that will read key and send it to server. So simple read key and send it. (Reading key taken from http://stackoverflow.com/questions/510357/python-read-a-single-character-from-the-user)
Create ctr_keyboard.py and copy:

import socket

TCP_IP = '127.0.0.1'
TCP_PORT = 5005
BUFFER_SIZE = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))


def _find_getch():
    try:
        import termios
    except ImportError:
        # Non-POSIX. Return msvcrt's (Windows') getch.
        import msvcrt
        return msvcrt.getch

    # POSIX system. Create and return a getch that manipulates the tty.
    import sys, tty

    def _getch():
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(fd)
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

        if ch == '\x03':
            raise KeyboardInterrupt
        elif ch == '\x04':
            raise EOFError
        return ch

    return _getch

getch = _find_getch()
try:
    while True:
        s.send(getch())

finally:
    s.close()

Now start game in one console and this script in other. When you press a key, you should see it in game console. To stop script you need to press ctrl+c in both terminals.

Its almost done. Now open player.py and add functions:

   def move_right(self):
        "move player right"
        self.pos_x += 1
        if self.pos_x > self.max_x:
            self.pos_x = self.max_x
        self.heading = "right"

    def move_left(self):
        "move player left"
        self.pos_x -= 1
        if self.pos_x < 2:
            self.pos_x = 2
        self.heading = "left"

Go back to game.py and change queue code to:

         try:
            key = self.queue.get(True, 0.05)
            print "tick: ", repr(key)
            if key == "a":
                self.player.move_left()
            if key == "d":
                self.player.move_right()
        except Queue.Empty:
            pass

This time when you run two scripts you will be able to move player with a and d keys!

Stop! Jenkins time !

Once again when we have some stable code its time to do automatic code review and fix problems.

As always I had wrong variable names 🙂 But his time I also rewrote keyboard script.

 Summary

In this part we have:

  • add player
  • create control server
  • create keyboard control
  • add event queue
  • make player sprite directional

Download source code

Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s