Sockets - Tic-tac-toe / Rooster Game MULTIPLAYER

Asked

Viewed 2,037 times

1

EDITED POST

I’m creating the so-called 'Tic-tac-toe' (or 'Rooster Game'), right now I’m trying to create a feature that allows two players on different devices - although on the same network - to play against each other.

This is an excerpt from the server code I’m trying to get the function to game() - that encompasses the whole game (inputs to the nicknames, frame, chances of winning, etc...), but in the part of the NCODE the supposed is to make this function run in the client program, and in this case it runs in the server program)!

Any help?

def Main():
    host = "127.0.0.1"
    port = 5000

    mySocket = socket.socket()
    mySocket.bind((host,port))

    mySocket.listen(1)
    conn, addr = mySocket.accept()
    print ("Connection from: " + str(addr))
    while True:
            data = conn.recv(1024).decode()
            if not data:
                    break
            print ("from connected  user: " + str(data))

            data = game
            print ("sending: ")
            conn.send(data().encode())

    conn.close()

if __name__ == '__main__':
    Main()
  • To my knowledge, Tictactoe is the game of old. I did not know the term "game of the rooster".

  • 2

    @Victorstafusa I’m from Portugal and not from Brazil, also was unaware of the term "game of old", hug!

  • 1

    Hi Swabadzu. Really, your question is wide, because you don’t exactly have a question/difficulty regarding the use of communication in networks. I suggest you first try to do some very basic sockets usage tutorial (to communicate text even between one point and another). This one, for example: http://wiki.python.org.br/SocketBasico. So you can think about how to implement what you need.

  • 1

    The communication will be essentially the same, but it is up to you to implement the remote command interface. That is, you will need to be able to send and receive some identifier that contains the "move" (in which position the player acted). There are several ways to think about it (it is not possible to "associate" the function playerX to a communication; you need to execute it on the client/player side, and then send the "instruction" with the action to the server side). But make sure before that you already know how to communicate (so I suggested the tutorial).

  • 1

    @Luizvieira Thank you very much, that was really helpful!

  • 1

    Changed code, more specific question!

  • Still confused. What is game? Would it be a function? If yes, when you do conn.send(data().encode()), Python first executes the function data() (which is equal to execute game()), then encodes what this function returns (data().encode()) and then sends this result encoded by the connection (conn.send(data().encode())). If you want to send the "object" (?) data, you should do conn.send(data.encode()). But this will depend on your object being capable of encoding (i.e., having a method encode).

  • @Luizvieira game() is a function that encompasses the whole game (inputs, the framework of the game, the chances of winning, basically everything) and I’m trying to pass it on to the customer, but it’s not giving

  • But why do you want to pass all this to the client? Ideally the client already has this code, and the server just pass it to state current game.

  • You’ve made a simpler program that communicates text between server and client? Already understood this part? If so, I suggest the following approach: edit the issue again and post the code you already tried (to demonstrate that you at least understood what network communication is like). Then, ask how to transmit state information (from a game) using this same mechanism. So I vote to reopen the question, and if possible I even try to give an answer.

  • 1

    @Luizvieira The excerpt of the code I put transmits text between the server and the client by changing the variable date from "game" to a string. I tested and it works, the only thing I changed was passing text to the full function itself ( game() )!

  • Okay. I’m going to vote to reopen. If you reopen, I’ll try to answer.

  • @Luizvieira I think it’s already reopened!

  • Ah, okay. I’ll try to prepare an answer. :)

Show 9 more comments

1 answer

3


By the example of code you provided, you seem to have understood that communication via socket allows you to send and receive messages. Ok. But you insist on trying to run a function remotely (or associate a function to a TCP/IP client), and this is not so trivial. Can sockets be used to do this? Of course, but you will need to implement a "remote call" feature manually. If this is your intention, look for libraries that already do this kind of thing (RPC, of Remote Procedure Call, or Remote Procedure Call), because reinventing the wheel is silly. Some examples can be found this link from Soen.

On the other hand, games do not usually make remote call procedures. They usually communicate between the server and the player(s) (s) the game state. So your problem is actually how to represent the state of the game so that it can be transmitted via sockets.

The most common is serialize/deserialize an object that represents the state of the game, and use the bytes that represent that state in the communication. Python has a lot of options for serialization, just take a look around (link suggestion). But in your case, as it is something trivial, my suggestion is to convert the tile matrix of the board into a string. Easy, fast and inspectable! (just print the result of save, for example).

So I made a class example that represents the game board:

import numpy as np
from random import *

class GameState:
    """
    Classe que representa o estado do jogo.
    """

    # -------------------------------------------------
    def __init__(self):
        """
        Construtor. Initializa o tabuleiro 3x3 vazio.
        """
        self.board = [[''] * 3 for n in range(3)]

    # -------------------------------------------------
    def save(self):
        """
        Salva os dados do tabuleiro para uma string.

        Gera uma string com as peças do tabuleiro separadas por
        ponto-e-vírgula (';'), de forma que o estado do jogo possa
        ser comunicado via socket.

        Retorno
        ----------
        data: str
            String de texto com os dados do tabuleiro separados por
            ponto-e-vírgula (';'), prontos para serem comunicados.     
        """
        return ';'.join([';'.join(x) for x in self.board])

    # -------------------------------------------------
    def restore(self, data):
        """
        Restaura os dados do tabuleiro a partir de uma string.

        Lê uma string com as peças do tabuleiro separadas por
        ponto-e-vírgula (';'), de forma que o estado do jogo possa ser
        comunicado via socket.

        Parâmetros
        ----------
        data: str
            String de texto com os dados do tabuleiro separados por um
            ponto-e-vírgula (';'), prontos para serem atualizados neste
            objeto.
        """
        self.board = np.reshape(data.split(';'), (3,3)).tolist()

    # -------------------------------------------------
    def print(self):
        """
        Imprime o tabuleiro em um formato visual.
        """
        print("+---+---+---+")
        for row in self.board:
            print('|{}|{}|{}|'.format(row[0].center(3, ' '), row[1].center(3, ' '), row[2].center(3, ' ')))
            print("+---+---+---+")

    # -------------------------------------------------
    def move(self, row, col, piece):
        """
        Faz uma jogada no tabuleiro, nas posições dadas.

        Parâmetros
        ----------
        row: int
            Número da linha no tabuleiro, no intervalo [0,2].
        col: int
            Número da coluna no tabuleiro, no intervalo [0,2].
        piece: str
            Letra com o símbolo jogado, entre as opções 'o' e 'x'.        
        """

        # Valida os parâmetros de entrada
        if row < 0 or row > 2:
            raise RuntimeError('Número de linha inválido: {}'.format(row))
        if col < 0 or col > 2:
            raise RuntimeError('Número de coluna inválido: {}'.format(col))
        piece = piece.lower()
        if piece != 'x' and piece != 'o':
            raise RuntimeError('Peça inválida: {}'.format(piece))

        # Verifica se a posição jogada está vazia
        if self.board[row][col] != '':
            raise RuntimeError('Posição do tabuleiro já preenchida: {}x{}'.format(row, col))

        # Faz a jogada
        self.board[row][col] = piece

    # -------------------------------------------------
    def moveRandom(self, piece):
        """
        Faz uma jogada aleatória no tabuleiro, em uma das posições vazias.

        Parâmetros
        ----------
        piece: str
            Letra com o símbolo jogado, entre as opções 'o' e 'x'.
        """

        # Cria uma lista com as posições vazias
        options = []
        for row in range(3):
            for col in range(3):
                if self.board[row][col] == '':
                    options.append((row, col))

        # Faz uma permutação aleatória nessa lista
        shuffle(options)

        # Faz a jogada na primeira posição da lista
        if len(options) > 0:
            row = options[0][0]
            col = options[0][1]
            self.move(row, col, piece)

The code is documented, but the cat’s jump is in the methods save and restore which respectively generate a string for you to send via socket and restore the board from a string received via socket. So you can use this to "ride" your game more or less that way:

Server:

import socket
from gamestate import GameState

# Cria o socket TCP/IP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Faz o bind no endereco e porta
server_address = ('localhost', 5000)
sock.bind(server_address)

# Fica ouvindo por conexoes
sock.listen(1)

while True:

    print('Aguardando a conexao do jogador')
    connection, client_address = sock.accept()

    try:
        print('Jogador chegou! :)')

        # Cria um tabuleiro de jogo vazio
        board = GameState()

        # Faz uma jogada aleatoria
        board.moveRandom('o')
        print('Eu joguei:')
        board.print()

        # Envia o tabuleiro para o jogador
        connection.sendall(board.save().encode('utf-8'))

        # Processa em loop
        while True:
            # Recebe a jogada do jogador
            data = connection.recv(1024)

            # Checa se a conexao do jogador foi terminada
            if not data:
                print('Jogador se foi. :(')
                break

            # Converte para string e restaura no tabuleiro
            board.restore(data.decode('utf-8'))

            print('O jogador jogou:')
            board.print()

            # Faz outra jogada aleatoria
            board.moveRandom('o')
            print('Eu joguei:')
            board.print()

            # Envia o tabuleiro para o jogador
            connection.sendall(board.save().encode('utf-8'))

    finally:
        # Clean up the connection
        connection.close()

Client:

import socket
import sys
from gamestate import GameState

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect the socket to the port where the server is listening
server_address = ('localhost', 5000)
print('Conectando ao servidor {} na porta {}'.format(server_address[0], server_address[1]))
sock.connect(server_address)

# Cria um tabuleiro de jogo vazio
board = GameState()

try:

    while True:

        # Recebe a jogada do servidor
        data = sock.recv(1024)
        board.restore(data.decode('utf-8'))

        print('O servidor jogou:')
        board.print()

        print('Faça a sua jogada:')
        print('------------------')

        nok = True
        while nok:
            row = int(input('Digite a linha:'))
            col = int(input('Digite a coluna:'))

            nok = False
            try:
                board.move(row, col, 'x')
            except:
                nok = True
                print('Linha ou coluna inválida. Tente novamente.')

        # Envia o tabuleiro para o servidor
        sock.sendall(board.save().encode('utf-8'))

finally:
    print('Encerrando o cliente')
    sock.close()

Result of a test session:

No servidor:
-----------------------------

Aguardando a conexao do jogador
Jogador chegou! :)
Eu joguei:
+---+---+---+
| o |   |   |
+---+---+---+
|   |   |   |
+---+---+---+
|   |   |   |
+---+---+---+
O jogador jogou:
+---+---+---+
| o |   |   |
+---+---+---+
|   |   | x |
+---+---+---+
|   |   |   |
+---+---+---+
Eu joguei:
+---+---+---+
| o | o |   |
+---+---+---+
|   |   | x |
+---+---+---+
|   |   |   |
+---+---+---+

No cliente:
------------------
Conectando ao servidor localhost na porta 5000
O servidor jogou:
+---+---+---+
| o |   |   |
+---+---+---+
|   |   |   |
+---+---+---+
|   |   |   |
+---+---+---+
Faça a sua jogada:
------------------
Digite a linha:1
Digite a coluna:2
O servidor jogou:
+---+---+---+
| o | o |   |
+---+---+---+
|   |   | x |
+---+---+---+
|   |   |   |
+---+---+---+
Faça a sua jogada:
------------------
Digite a linha:
  • 1

    Thank you very much for the reply, it is certainly much more elaborate than I expected, thank you very much!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.