python - covery game/college project

Asked

Viewed 12,290 times

3

I need to create a program that simulates a kind of "cover game" using only boolean variables, basic functions and repeaters. So far my program finds itself like this:

print()
print('=================================================')
print('         Bem-vindo ao Jogo da Cobrinha!          ')
print('=================================================')
print()

nlinhas = int(input('Número de linhas do tabuleiro : '))
ncols   = int(input('Número de colunas do tabuleiro: '))
x0      = int(input('Posição x inicial da cobrinha : '))
y0      = int(input('posição y inicial da cobrinha : '))
t       = int(input('Tamanho da cobrinha           : '))

# Verifica se corpo da cobrinha cabe na linha do tabuleiro,
# considerando a posição inicial escolhida para a cabeça
if x0 - (t - 1) < 0:
    # Não cabe
    print()
    print("A COBRINHA NÃO PODE FICAR NA POSIÇÃO INICIAL INDICADA")

else:

    ''' Inicia a variável d indicando nela que t-1 partes do corpo
        da cobrinha estão inicialmente alinhadas à esquerda da cabeça.
        Exemplos:
           se t = 4, então devemos fazer d = 222
           se t = 7, então devemos fazer d = 222222
    '''
    d = 0
    i = 1
    while i <= t-1:
        d = d * 10 + 2
        i = i + 1

    # Laço que controla a interação com o jogador
    direcao = 1
    while direcao != 5:
        # mostra tabuleiro com a posição atual da cobrinha
        imprime_tabuleiro(nlinhas, ncols, x0, y0, d)

        # lê o número do próximo movimento que será executado no jogo
        print("1 - esquerda | 2 - direita | 3 - cima | 4 - baixo | 5 - sair do jogo")
        direcao = int(input("Digite o número do seu próximo movimento: "))

        if direcao != 5:
            # atualiza a posição atual da cobrinha
            x0, y0, d = move(nlinhas, ncols, x0, y0, d, direcao)

print()        
print("Tchau!")

# ======================================================================

def num_digitos(n):
    """ (int) -> int

    Devolve o número de dígitos de um número.

    ENTRADA
    - n: número a ser verificado 

    """
    # Escreva sua função a seguir e corrija o valor devolvido no return

    n = int(input('qual é o numero: ')) 
    num_digitos = 0
    i = 0
    while n != 0:
        i = n % 10
        n = n // 10 
        num_digitos = num_digitos + 1
        i = i + 1

    print("Número de digitos é", num_digitos)

    return num_digitos

# ======================================================================
def pos_ocupada(nlinhas, ncols, x, y, x0, y0, d):
    """(int, int, int, int, int, int, int) -> bool

    Devolve True se alguma parte da cobra ocupa a posição (x,y) e
    False no caso contrário.

    ENTRADAS
    - nlinhas, ncols: número de linhas e colunas do tabuleiro
    - x, y: posição a ser testada
    - x0, y0: posição da cabeça da cobra
    - d: sequência de deslocamentos que levam a posição da cauda da cobra
         até a cabeça; o dígito menos significativo é a direção na cabeça

    """

    # Escreva sua função a seguir e corrija o valor devolvido no return
    Achei = False

    while (d != 0):
        resto = d % 10
        d = d // 10

        if resto == 1:
            x0 = x0 + 1

        if resto == 2:
            x0 = x0 - 1

        if resto == 3:
            y0 = y0 - 1

        if resto == 4:
            y0 = y0 + 1

        if x == x0 and y == y0:
            achei = True


    return True


# ======================================================================
def imprime_tabuleiro(nlinhas, ncols, x0, y0, d):
    """(int, int, int, int, int, int)

    Imprime o tabuleiro com a cobra.

    ENTRADAS
    - nlinhas, ncols: número de linhas e colunas do tabuleiro
    - x0, y0: posição da cabeça da cobra
    - d: sequência de deslocamentos que levam a posição da cauda da cobra
         até a cabeça; o dígito menos significativo é a direção na cabeça

    """


     # Escreva sua função a seguir
    print("Vixe! Ainda não fiz a função imprime_tabuleiro()!")



# ======================================================================
def move(nlinhas, ncols, x0, y0, d, direcao):
    """(int, int, int, int, int, int) -> int, int, int

    Move a cobra na direção dada.    
    A função devolve os novos valores de x0, y0 e d (nessa ordem).
    Se o movimento é impossível (pois a cobra vai colidir consigo mesma ou
    com a parede), então a função devolve os antigos valores e imprime a
    mensagem apropriada: "COLISÃO COM SI MESMA" ou "COLISÃO COM A PAREDE"

    ENTRADAS
    - nlinhas, ncols: número de linhas e colunas do tabuleiro
    - x0, y0: posição da cabeça da cobra
    - d: sequência de deslocamentos que levam a posição da cauda da cobra
         até a cabeça; o dígito menos significativo é a direção na cabeça
    - direcao: direção na qual a cabeça deve ser movida

    """

    # Escreva sua função a seguir e corrija o valor devolvido no return
    print("Vixe! Ainda não fiz a função move()!")

    return x0, y0, d

# ======================================================================
main()   

I’ve already managed to do the functions num_digitos and pos_ocupada, but I don’t know how to start the function that prints the game board or the one that moves the snake.

2 answers

5

Creating a snake game in the terminal, from scratch

Reviewing your question - I didn’t really answer you need - it’s a college exercise reconstructing the steps of the covering from the direction - and it’s s[o theorist, with m 0 concern whether the game would be real or not.

Later on, maybe,:

Full Cover Game Tutorial in text mode

Good - you have another problem there - if the game needs to be in text mode, there is no standardized way between operating systems like Linux and OS X and Windows on how to draw an interactive screen on the terminal - or read the keyboard in real time (without the player having to press <enter> after each movement).

That is - this is a game that was once simple at a time when text interfaces offered a fixed control option. Now - just these functions of "see what character is in such position" and "print a character in such position" are what let create games like this in a simple way. I’m going to suggest a library to make such a little game in text mode - and then with a few examples, you’ll see that the game is at once simpler than what you’re doing, and more fun in terms of programming.

Nowadays, in the Linux and OS X environments, with Python, you can use the "curses" library that comes with Python. Windows, by default, does not work "curses" and you will have to use the library msvcrt . You could determine that you will only need 3 things: (1) read one of the arrow keys in "real time" - (that is, without waiting for enter), (2) be able to clear the screen and (3) be able to print a character in an arbitrary position (row and column) of the screen. Hmmm, seeing the documentation -- "msvcrt" does not allow (3) - you are back in the bush without dog.

Ah - (santo Google) - there is a way to install "curses" for Windows - is in this accepted answer of stackoverflow in English: https://stackoverflow.com/questions/32417379/what-is-needed-for-curses-in-python-3-4-on-windows7

That being said, Curses is a bit boring - your program either prints and uses input, as you do, or does everything for curses - its documentation is here: https://docs.python.org/3/library/curses.html - so I’m not even going to try to put up with this bunch of questions and answers to start the game you did. (and let’s face it - it’s pretty cool now that you already know about the input and put its value in a variable - but who wants to answer a lot of questions just to start the game of the hedge? :-) )

I am consulting the official tutorial of curses of the Python site at https://docs.python.org/3/howto/curses.html#curses-howto and adapting to your question.

Let’s go in parts - first, let’s put a character in the text terminal that can be moved with the arrow keys, using the "curses" library. Unicode has a lot of cool characters, but since it’s supposed to be "root", let’s use a "*".

import curses
import locale
import time
locale.setlocale(locale.LC_ALL, '')

def main(screen):
    curses.curs_set(0)
    screen.nodelay(True)

    y, x = 20, 20
    direction = (0, 1)
    while True:
        key = screen.getch()
        if key == curses.KEY_LEFT:
            x -= 1
        elif key == curses.KEY_RIGHT:
            x += 1
        elif key == curses.KEY_DOWN:
            y += 1
        elif key == curses.KEY_UP:
            y -= 1
        elif key in (0x1b, ord("q")): # <ESC>
            break
        screen.addstr(y, x, b"*")
        screen.refresh()
        time.sleep(0.1)


curses.wrapper(main)

You notice that the curses have a lot of bureaucracies - we have to call the setlocale in order to be able to print Unicode characters (in a terminal with Unicode, of course), we have to call curs_set, nodelay to delete the cursor from the text mode and not to wait for the <enter>, as a input common. In addition, the call wrapper calling the function main already initializes the screen, and configures some 4 or 5 things.

With those calls out of the way, we can start thinking about the program - I initially put "*" at the "20, 20" position (why ask the user that? - the game may have some pre-configured start positions, or it may simply draw a position, but it will not change the game by asking the initial position of the cover for the user - except for the worst).

In the body of this "while True" I establish what I call the game’s "main loop" - is the code Techo that runs the entire game frame - and that will update the position of the elements, read the keyboard, etc -- however sophisticated a game is - can be the GTA VIII - , it will have to have a "main loop" - in some game programming frameworks, the loop can be embedded in the framework, and you only configure some of its functions that will be called when events happen - but the loop is there.

Inside the loop, the first thing is to call screen.getch() - is a curses method (actually in the "screen" object that wrapper passes to our function) that reads the numeric code of a single key. How do we define the nodelay above, this call returns instantly - if no key is pressed, it will have a special value.

Then several lines of pure Python programming: 5 ifs comparing the code pressed with those of the arrow keys - as these values, are not standardized in ASCII or other encoding - unlike the "q" key or even the "ESC", we have to compare with preset constants in the curses module itself. (Other frameworks for games that allow keyboard reading also have to use this feature to have arrow code, function keys (F1, F2, ...), "prtscr", etc...)

But - so, it’s no secret if the user pressed "right arrow", we added 1 on the x coordinate, and so on.

Then the call to function addstr(linha, coluna, caractere) puts the character in the set position. Ready - everything we needed! Only not - reading the doc, we see that it just draws even when we call the method refresh - and finally, a 1/10 second scheduled pause - so that the asterisk does not move as quickly as possible. The duration of this break is that you will then control, according to the difficulty level, to determine the speed of the cover.

One last thing about the "bureaucracy" of using curses: everything you send to the screen, and take it back is in "bytes" - in Python 3, we work directly with "text" (the Python 3 strings are the old Python 2 "Unicode" object) - which means that curses know nothing of accented characters, or emojis - you have to pass everything chewed to it - normal Python strings have to be transformed into Bytes with your "Encode" method - or, in this case, as we are only putting simple characters, we can write the direct bytes by placing a b" to open the string in the program code.

great - test so far - and keep in mind that when they created the first variants of the copper, in the 70’s, it was that terminal environment that people had.

Now, to begin to turn this into the game of the little snake: (1) it cannot stand still, it must always have a direction. If you use a single number for the direction as in your code, every time you use the direction you will have to use an "if" sequence to update anything. direcao = 3: y coordinate should be updated? Up or down?

Since we will always have to update the x and y coordinates, the direction can be a sequence of two numbers, already with the values to update each variable.

import curses
import locale
import time
locale.setlocale(locale.LC_ALL, '')

def main(screen):
    curses.curs_set(0)
    screen.nodelay(True)
    y, x = 20, 20
    direction = (1, 0)
    while True:
        key = screen.getch()
        if key == curses.KEY_LEFT:
            direction = (-1, 0)
        elif key == curses.KEY_RIGHT:
            direction = (1, 0)
        elif key == curses.KEY_DOWN:
            direction = (0, 1)
        elif key == curses.KEY_UP:
            direction = (0, -1)
        elif key in (0x1b, ord("q")): # <ESC>
            break
        x += direction[0]
        y += direction[1]
        screen.addstr(y, x, b"*")
        screen.refresh()
        time.sleep(0.1)

curses.wrapper(main)

Now, I don’t even need to go into explaining any more - notice that the game of the little snake starts to appear naturally - and it’s just a matter of programming now. And, surprise: you don’t "redesign the whole screen" in this type of game - simply, add a character in the new position of the head of the hedgerow, leaving those who were already there - that’s how the game got fast on the old computers where it was created. (A modern game redesigns the entire scene, all frames, sometimes recalculating tens of thousands of polygons, Stretching and illuminating everything-if it’s a 3D game)

What we need for the game to be complete: make it cover up by "blacking out" after it has walked its size - and make it detect a collision. For this second part, we can still use a function of curses to "see" the character in a given position - this exists - and it is what allowed the little games of old to be simpler than today. Why the correct today would be instead of "look" at the screen, check using the data structures we have, if the cover hit something - be a part of itself, a wall, an "apple" (which makes it increase in size and the game progress), etc... Mainly because if the game is in graphic mode, or even in 3D, it is not possible to "recognize" what is in a position where the cover moves. (in this case with the curses, we could see the character and use an "if" to know if it is a "@" representing an apple, or a "#" representing a wall, for example). This answer has already become a book chapter, but it doesn’t have to be the whole book - I’ll keep the simple case where we "look" at the screen with the method screen.instr(linha, coluna):

import curses import locale import time locale.setlocale(locale.LC_ALL, '') encoding = locale.getpreferredencoding()

def main(screen): curses.curs_set(0) screen.nodelay(True) y, x = 20, 20 Direction = (1, 0) while True: key = screen.getch() if key == curses.KEY_LEFT: Direction = (-1, 0) Elif key == curses.KEY_RIGHT: Direction = (1, 0) Elif key == curses.KEY_DOWN: Direction = (0, 1) Elif key == curses.KEY_UP: Direction = (0, -1) Elif key in (0x1b, Ord("q")): # break x += Direction[0] y += Direction[1] if screen.instr(y, x, 1) != b" ": screen.addstr(10, 15, "You died!".NCODE(encoding)) screen.refresh() time.Sleep(3) break screen.addstr(y, x, b"*") screen.refresh() time.Sleep(0.1)

curses.wrapper(main)

And finally, we create a desired initial length for the cover, store all the coordinates (x, y) it went through - and when it reached that length, erase the last "*" at that position. Thanks to the Python lists, it’s trivial to do this. (but you can learn more and do it more elegantly using the collections.deque). That’s literally 6 more lines of code - and the game would be "complete", except it’s no fun at all. I take the opportunity to include the "apple" that increases the length of the covering in 5 strokes at a time. No secret either - we use the module "Random" to draw the apple. We put scores and a check of the screen limits (the curses exposes the terminal size in curses.COLS and curses.LINES) - I think that’s all there is to it:

import curses
import locale
import time
import random
locale.setlocale(locale.LC_ALL, '')
encoding = locale.getpreferredencoding()

def sorteia_maca(screen):
    pos = random.randrange(0, curses.COLS), random.randrange(0, curses.LINES)
    screen.addstr(pos[1], pos[0], b"@")

def main(screen):
    curses.curs_set(0)
    screen.nodelay(True)
    y, x = 20, 20
    direction = (1, 0)
    comprimento = 5
    corpo = []
    sorteia_maca(screen)
    pontuacao = 0
    while True:
        key = screen.getch()
        if key == curses.KEY_LEFT:
            direction = (-1, 0)
        elif key == curses.KEY_RIGHT:
            direction = (1, 0)
        elif key == curses.KEY_DOWN:
            direction = (0, 1)
        elif key == curses.KEY_UP:
            direction = (0, -1)
        elif key in (0x1b, ord("q")): # <ESC>
            break
        x += direction[0]
        y += direction[1]

        corpo.insert(0, (x, y))
        if len(corpo) > comprimento:
            final = corpo.pop()
            screen.addstr(final[1], final[0], b" ")

        cabeca_em = screen.instr(y, x, 1)
        if cabeca_em == b"@":
            pontuacao += 5
            comprimento += 5
            sorteia_maca(screen)

        elif cabeca_em != b" " or x >= curses.COLS or y > curses.LINES or x < 0 or y < 0:
            screen.addstr(10, 15, "Você morreu! Pontos: {}".format(pontuacao).encode(encoding))
            screen.refresh()
            time.sleep(3)
            break

        screen.addstr(y, x, b"*")
        screen.refresh()
        time.sleep(0.1)

curses.wrapper(main)

Ready - completely playable! You can increment it with several things now - for example, by making the apple change place from time to time, you can put "levels" with "walls" and increasing the speed, you can switch to a graphical screen using "pygame" or another framework, instead of keeping itor in text mode, you can write a class in object orientation to cover - allowing you to have more instances of it on the same screen, the others being controlled by the computer (or - connect the game in network, and have a cover controlled by another player)

2

Answering specific questions of the implementation in the question, about the game of the hedge

Ok - I’ll leave the other answer as a reference for anyone who wants to implement the Python Cover Game. Here, I’ll comment on your problem -which is something using "input" that looks like the game of the hedge.

Moving the snake

In the proposed exercise, the cover is simply given by an integer number, with as many digits as the actual length of the snake. When obtaining the die from a new direction, simply multiply the previous number by 10 (thus adding a "0" at the end) and add the number of a digit representing the current direction. Ex.: if the current cover is represented by the sequence 2232 and moves again in the direction 3, the number will become 22323. To limit the size of the cover, you can take the rest of the division 10 ** N (N being the size of the cover) by this number. In this example, if the cover has to be size "4", the rest of the 22323 by 10000 (10 ** 4) division is 2323 - the right-most number has been discarded. - you don’t need a function just for this insertion: they are two lines of code - but you can do it if you want.

(On the other hand, the main part of your program is in the module body, outside of any function: this is not only a considerable mess, it won’t even work - since the lines in the module body are executed before the lines that define the functions themselves, lower down in the program. That main part of the program must be placed in a function - even if called only once - and in the last line of the program you call this function)

(tip 2: in the real world, Python has "lists" to store this type of data saved as an integer - and languages that are not Python and have no lists, such as C, also have no arbitrary integers that can be used for this purpose, so this is a restriction well artificial exercise)

Printing the board

This one may be the heart of the problem. The point is that without a terminal library that allows you to write a character in any position you want, you can only print the screen "top to bottom": this is - first the top line, then the line below that, and so on to the last line. So, if you leave it to "discover" during the impression that the cover "came down", and want to print a part of her body a line up, there’s no way.

Are two possible paths:

  1. Create a 2D matrix in memory before to print, with each position representing a position on the screen - there you follow the directions of the cover, "Drawing it" in this matrix - the advantage is that you can draw at any point. And in the next step, you print this matrix, starting from the top lines - as the whole snake is drawn, there will be no problems .

  2. You can by printing each character of each line, from left to right and from top to bottom, call a function like the one you want to be yours pos_ocupada: If it returns true to that position, you print there a "*" to represent the snake’s body, otherwise print a " ". This is the easiest option to program - IF - the "pos_occupied" function already works - but it doesn’t seem to be the case (let’s see her code now). On the other hand it is also a solution that requires that all snake positions be calculated multiple times in each game frame. This game is asynchronous, and 10 or 20 cover positions are something insignificant compared to the speed of a PC where you run Python - but it’s something inefficient by nature - for every screen character, let’s say 40 columns per 20 rows, You will calculate the entire cover. In a job interview, it means you get rejected in the act. In real work, it means an excerpt of the game that is hundreds to thousands of times slower (and consuming more drums) than the other way. It’s not good to encourage a thought like.

The - I went to see the code of your function "pos_occupied" - she is fine - only, without even looking very carefully, I can say that you never ran the function -and do not even know if it works. Why there are two errors: the variable "found" is spelled one hour as Achei another as achei - and you never use the result of this variable, it simply returns "True" always. And in fact, you don’t even need this variable - if at any time the condition x == x0 and y == y0, the cover occupies the position "x, y", and you can return True. If you reach the end of the while, you can return False. So just change the end of this function to:

   if x == x0 and y == y0:
        return True

return False

(Read about the "Enhance assignment Operators" and see how you should be writing x0 += 1 instead of x0 == x0 + 1). As to the if in its function - you do not speak in its utterance, and as it does not get to imprimri nothing it is not possible to know - but in computation, as a rule, the ordered "y" starts at 0 "at the top" and increases downwards - principle n oo mode text, we speak of "line 1, line 2, line 3", one below the other. How this crazy system that your teacher invented finds the previous positions of the body of the copper analyzing the direction "where it came from" (that is, when the cap goes left: direction "1", it means that the part after the head is the "right" of the head. The "pos_occupied" function then moves to "right" when finding the direction code for "left" that the user has entered). OK - I don’t know if this part of the code is yours or was passed as a hint by the teacher - but be sure to understand this earlier part, in parentheses, before continuing.

Anyway, for a coordinate system where y grows down, the "if" relative to "y0" are inverted. If the snake came down (direction 4) the anterior part of her body is on the top line, then you need to subtract 1 from y0 - the code is summing instead of subtracting.

Well, with the "pos_occupied" sorted, it is very simple to use the method (1) above: a for y running along all the lines, inside it a for x traverse all columns - and then you print a "*" or " character depending on posocupada(...,x ,y) return true or false. (In Python3, the print function allows printing a single character, without changing line if you pass the parameter with name end with the value of an empty string: print(caractere, end="") (and at the end of "for x", before starting a new interaction of "for y", use an empty print to change lines. (Ufa - writing this paragraph was much more tiring than writing the direct code, but I have to leave something for you to do).

Possibly this method (1) is what your teacher had in mind when proposing the exercise. ESPECIALLY if he gave the hint of writing this function "pos_occupied". But if it were my exercise, I wouldn’t give an A, and instead of proof, it was a job interview....

For method (2): The first thing is to create a two-dimensional matrix - it’s kind of funny, but Python doesn’t have something like that ready. With the features of "Perator overloading" it would be really easy to create a specialized object that cites a pair of elements as index, which could function as coordinates. But it’s not your exercise. Another way is to use a dictionary - Python Permit that a tuple "x,y" is a dictionary key - so if a function with code much like "pos_occupied" creates an element in an empty dictionary at all positions "x0" and "y0" that are busy, you can read this dictionary at the time of printing - and the snake body will be calculated only once. In this case, the dictionary would be functioning as a "sparse matrix" - it’s good code - simple, efficient - but you has that learn at some point to create a 2D matrix in Python, which is by making a "list of lists" and make use of it, and join its elements to create printing with a single print. (Returning to the test and job interview: I would be more impressed, IN this case by the code using dictionary - but not if the person had used a ready recipe and did not know how to manipulate a list list as a 2D matrix.

Code to create matrix:

tela = []
for y in range(linhas):
   tela.append([" "] * colunas)

Ready, now you can use positions like screen[y0][x0] to store a value in a single position of your game - be a "*" for the body of the cover, or other characters if the game came to have "apples" or "walls".

You can create a blank screen at the beginning of the "pos_occupied" function (or a function with the same algorithm, but another name), mark each position of the cover, doing, inside the while tela[x0][y0] = "*"and return the "filled screen".

To print this structure, we use the method join of strings. join Cite a sequence as parameter, and create a single string in which the elements of the sequence are pasted together, separated by the original string of the join (which is a method) - if the string is a line break character ("n"), the result is a string that can be printed, and will print each element of the "jointed" list on a different line. If it is an empty string, all strings in the list are simply pasted in sequence.

So it’s possible to do print ('\n'.join(''.join(linha) for linha in tela)) to print this "canvas" all at once.

(Ready, now you have how to proceed with the exercise)

  • Make a Edit in the above answer, Problem and Explanation, and attach that answer.

  • 2

    Really? The platform Recommends 1 answer per question. But are two completely different things, and the text would get very large. If algebraic search is for "Python cover game" - the answer above is good. To answer the question of the OP, this is better - not I why not keep separate - if it was forbidden to have two answers from the same author, the platform simply would not allow.

  • (Just so we’re clear, "Seriously?" that the comment above was not meant to be offensive - it was to ask if @Edilson really wanted me to do it. Only at the time of reading it seemed like an ill-mannered affront - sorry. Revisiting the answers now, it seems like it really makes sense to keep the two answers)

Browser other questions tagged

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