Question

I'm writing a very simple farming game in Python using curses. At this point I have been successful in allowing the player (just a "@" character) to move around within a window.

I have a few files with ascii-art that I print to the window as things to populate the world in which the player can move around. For example, I have a file, named "house", that contains:

 _ . ^ . _
/____.____\
|         |
| ## _ ## |
|_""_H_""_|

I have a Thing class as follows:

class Thing(object):    

    def __init__(self, Xstart, Ystart, looksLike, list, window):
        self.Xstart = Xstart
    self.Ystart = Ystart
    self.X = Xstart
    self.Y = Ystart

    self.looksLike = looksLike

    self.boundries = []
    self.find_boundries()

    list.append(self)

    self.draw(window)


def find_boundries(self):

    file = open(self.looksLike).readlines()
    for line in file:
        for char in line:
        if char == '\n':
            pass
        elif char == ' ':   # skip all whitespace
            self.X += 1
        else:
                self.boundries.append([self.X, self.Y])
            self.X += 1

        self.Y += 1
        self.X = self.Xstart

    self.X = self.Xstart    # reset x & y to starting coordinates
    self.Y = self.Ystart


def draw(self, window):

    #file = open(self.looksLike).readlines()

    #for line in file:
    #    window.addstr(self.Y, self.X, line)
    #    self.Y += 1

#self.Y = self.Ystart

    file = open(self.looksLike).read()

    for char in file:
        window.addch(self.Y, self.X, char)
    if char == '\n':
            self.Y += 1
    self.X = self.Xstart
    else:
    self.X += 1

self.X = self.Xstart    
self.Y = self.Ystart

Thus the constructor for my Thing class takes a filename as an argument (looksLike) and the draw method opens the file, reads it, and prints its contents to the window. I can, then, create a house object, pass my "house" file as an argument, and my ascii house will be printed to the window.

The problem is that once the object is printed to the window, when I move the player to the right of the printed object the player disappears. Above, below, and to the left of the printed object, however, the player stays in view. For example,

 _ . ^ . _
/____.____\
|         |
| ## _ ## |
|_""_H_""_|
                  @

In this position the "@" character is visible, but if I move one space up, it disappears. If I continue moving the player up, the "@" will reappear after it moves beyond the topmost character of the house.

I assume this issue is due to the nature of both addstr() and addch() ( I have tried both) printing whitespace until the end of the window, but I have been unable to find any documentation on this.

I have considered creating a new window for each object printed, but this seems like it would get quite cumbersome when more than a few objects are printed to the window. Also, I hope to define borders around the objects printed to the screen that are shapes other than just squares or rectangles.

Is there anyway to print from a file to the window without the trailing whitespace and without creating a new window for each printed object?

Was it helpful?

Solution

I assume this issue is due to the nature of both addstr() and addch() ( I have tried both) printing whitespace until the end of the window,

Why do you assume that?

First, you never actually call addstr in the code you've shown us, so that can't be it.

As for addch, it very definitely should not do that—as you could see by just, e.g., drawing from right to left. Or by running this trivial test code:

# usual curses setup
stdscr.addch(10, 10, 'a')
stdscr.addch(10, 9, 'b')

If you're not seeing the a in that test program, there's something wrong with your terminal. But if you are, then your assumption is wrong, and it has nothing to do with addch.


Almost certainly the problem is that you actually have spaces in the house file. In curses, if you draw a character on top of another character, it replaces the old character, it doesn't try to merge them or overstrike them or anything like that. (Which is good, because most consoles don't have any way to do any such thing…)

If the new character is a space, it just replaces the old character with a space. Exactly like you're seeing.


So, the fix is to remove all those spaces on the end of each line, right? Well, you could do that. Or you could just rstrip() each line. (You don't need the \n; you can tell that you've gotten to the end of a line by the fact that you've finished iterating over the whole line, right? Well, you can in your file = open(…).readlines() code, or in code that didn't bother with the readlines() and just looped over the file itself; you can't in your different file = open(…).read() code, but I don't know why you're doing it differently there in the first place.)

Or, since your find_boundries function very carefully skips over spaces, maybe you wanted to do the same thing in draw but just forgot to? If so, just write the code you intended to skip over the spaces.

But there's a much simpler solution to the whole problem: Just draw the @ after the house instead of before it, and this won't even be an issue in the first place. Of course that means that if the player is in the same place as the house, he'll show up "outside" of it rather than being hidden "inside"—but you already appear to have code to prevent that from ever happening, so it shouldn't matter who it would look like if it happened.

OTHER TIPS

Onfortunately I have never used curses, and I cannot see your player class.

Nevertheless maybe this snippet might give you some ideas (press 'x' to exit game) (use WASD to move the player) (requires ANSI-enabled console:

#! /usr/bin/python3

class _GetchUnix:
    def __init__(self):
        import tty, sys

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

getch = _GetchUnix ()

house = ''' _ . ^ . _ 
/____.____\\
|         |
| ## _ ## |
|_""_H_""_|'''

class Screen:
    def __init__ (self, width, height, background):
        self.width = width
        self.height = height
        self.bg = '\x1b[{}m'.format (40 + background)
        self.clear = '\x1b[0m'
        self.objects = []

    def __iadd__ (self, obj):
        self.objects.append (obj)
        obj.screen = self
        return self

    def render (self):
        print ('\x1b[1;1H', end = '')
        for y in range (self.height):
            for x in range (self.width):
                print (self.bg + ' ' + self.clear, end = '')
            print ()
        for obj in self.objects: obj.render ()
        print ('\x1b[{};1H'.format (self.height) )

class Object:
    def __init__ (self, graphics, foreground, background, x, y):
        self.graphics = graphics.split ('\n')
        self.fg = '\x1b[{}m'.format (30 + foreground)
        self.bg = '\x1b[{}m'.format (40 + background)
        self.clear = '\x1b[0m'
        self.x = x
        self.y = y

    def render (self):
        for y, line in enumerate (self.graphics):
            print ('\x1b[{};{}H'.format (self.y + y, self.x), end = '')
            print (self.fg + self.bg + line + self.clear)

    def collide (self, x, y):
        if y < self.y: return False
        if x < self.x: return False
        if y > self.y + len (self.graphics) - 1: return False
        if x > self.x + len (self.graphics [y - self.y] ): return False
        return True

    def move (self, dx, dy):
        nx, ny = self.x + dx, self.y + dy
        if ny < 1: return
        if ny > self.screen.height: return
        if nx < 1: return
        if nx > self.screen.width: return
        for obj in self.screen.objects:
            if obj == self: continue
            if obj.collide (nx, ny): return
        self.x, self.y = nx, ny

house = Object (house, 0, 7, 6, 3)
player = Object ('@', 1, 3, 10, 10)
s = Screen (40, 20, 3)
s += house
s += player
while True:
    c = getch ()
    if c == 'x': break
    if c == 'w': player.move (0, -1)
    if c == 's': player.move (0, 1)
    if c == 'a': player.move (-1, 0)
    if c == 'd': player.move (1, 0)
    s.render ()

Here a screen shot:

enter image description here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top