User Tools

Site Tools


pygame_terminal_ii

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
pygame_terminal_ii [2023/11/06 00:53] appledogpygame_terminal_ii [2023/11/06 01:58] (current) appledog
Line 66: Line 66:
  
 update() is important because, until now, we have been responding to the user's input as it happens, and we have been 'tagging along' mob movement and game system updates to this directly (as in the robots game). However, to be more strict we should keep these in an update() function which updates the game state even if the player is not typing anything. This allows for more freedom in how the game could be designed, such as in the javascript [[Shooty Ships]] game. update() is important because, until now, we have been responding to the user's input as it happens, and we have been 'tagging along' mob movement and game system updates to this directly (as in the robots game). However, to be more strict we should keep these in an update() function which updates the game state even if the player is not typing anything. This allows for more freedom in how the game could be designed, such as in the javascript [[Shooty Ships]] game.
 +
 +== Game state and update()
 +Since we are going to have a game state that will be update()'d every frame, there needs to be a standard way of representing the state of the screen. Normally this is done by drawText()'ing whatever you need, and it just works. However, this is a simplistic approach which will hamstring our ability to fully develop our terminal-esque simulation. What we want to do next is follow the path of VT development in the past and move from a screen towards a terminal. Thus, the terminal class is born.
 +
 +First let's add and discuss the class, then we will integrate it into the Game class.
 +
 +== Class Character
 +You will first need a simple Character class for Class Terminal:
 +
 +<Code:Python|Character.py>
 +class Character:
 +    def __init__(self, ch = ' ', color = 'gray'):
 +        self.ch = ch
 +        self.color = color
 +</Code>
 +
 +This just allows us to move color data to a per-color basis. Later we can add a background color or define ANSI colors. We will save that for Pygame Terminal III.
 +
 +== Class Terminal
 +<Code:Python>
 +from Character import Character
 +import threading
 +
 +class Terminal:
 +    def __init__(self, cols, rows):
 +        self.cols = cols
 +        self.rows = rows
 +        self.cx=0
 +        self.cy=0
 +        self.interval = 0.535 # 535ms
 +        self.cc=False
 +        self.repeat_timer()
 +
 +        self.buf = [[0 for x in range(cols)] for y in range(rows)]
 +
 +        for y in range(rows):
 +            for x in range(cols):
 +                self.buf[y][x] = Character()
 +
 +    def setch(self, x, y, ch, color):
 +        self.buf[y][x].ch = ch
 +        self.buf[y][x].color = color
 +
 +    def putch(self, ch, color):
 +        self.buf[self.cy][self.cx].ch = ch
 +        self.buf[self.cy][self.cx].color = color
 +        self.cx = self.cx + 1
 +        if self.cx >= (self.cols-1):
 +            self.cx = 0
 +            self.cy = self.cy + 1
 +
 +        if self.cy >= (self.rows-1):
 +            print("scroll screen")
 +
 +    def delch(self):
 +        self.cx = self.cx - 1
 +        if self.cx < 0:
 +            self.cx = 0
 +
 +        self.buf[self.cy][self.cx].ch = ' '
 +        self.buf[self.cy][self.cx].color = 'gray'
 +
 +    # Function to schedule the timer to repeat
 +    def repeat_timer(self):
 +        self.cc = not self.cc
 +        #print(f"Cursor state: {'On' if self.cc else 'Off'}")
 +        if self.interval > 0.1:
 +            threading.Timer(self.interval, self.repeat_timer).start()
 +</Code>
 +
 +=== Cursor
 +One notable addition here is the cursor. I believe that 535ms is the speed of the original IBM-PC DOS terminal. On WIN11 systems today (2023) and on some ubuntu systems it is shown as 530ms. On a C-64 I believe the speed was 750ms.
 +
 +We couldn't do the cursor unless we had access to a map of the screen to preserve the underlying character's state, so having the Terminal class was a prerequisite to doing a cursor of course.
 +
 +=== draw() in Terminal
 +If you have been following along from [[Pygame Terminal]] you will see that drawText() is in Game, and no draw() function was provided by Terminal. However, one should be.
 +
 +In an early version of [[PyHack]] I copied the levelmap into the buf before drawing it:
 +
 +<Code:Python>
 +    def drawGame(self):
 +        for y in range(self.level.h):
 +            for x in range(self.level.w):
 +                self.term.setch(x, y+2, self.level.map[y][x], 'gray')
 +
 +        for y in range(self.term.rows):
 +            for x in range(self.term.cols):
 +                ch = self.term.buf[y][x].ch
 +                color = self.term.buf[y][x].color
 +                self.drawText(x, y, ch, color)
 +
 +        # draw cursor
 +        if self.term.cc:
 +            self.drawText(self.term.cx, self.term.cy, '_', 'gray')
 +</Code>
 +
 +This was the initial integration, but it seemed to make more sense that the drawText part and the cursor part could have (should have) been included in Terminal. When we try to do this, we see that the drawText() method shouldn't be in Game, but in Window(!!) This way, Game can pass it to Terminal, and not have to pass itself (Game) to Terminal. That makes more flow-sense.
 +
 +First, add this to Terminal:
 +
 +<Code:Python|Added to Class Terminal>
 +    def draw(self, window):
 +        for y in range(self.rows):
 +            for x in range(self.cols):
 +                ch = self.buf[y][x].ch
 +                color = self.buf[y][x].color
 +                window.drawText(x, y, ch, color)
 +
 +        # draw cursor
 +        if self.cc:
 +            window.drawText(self.cx, self.cy, '_', 'gray')
 +</Code>
 +
 +Next, you can add this to Window:
 +
 +<Code>
 +   def drawText(self, at_x, at_y, text, color):
 +        text_surface = self.font.render(text, False, color)
 +        x = self.fontwidth * at_x
 +        y = self.fontheight * at_y
 +        self.screen.blit(text_surface, (x+2, y))
 +<Code>
 +
 +After you delete drawText() from Game, The terminal will have been fully abstracted out of the Game class. The Game class makes updates using term.setch or term.putch, and there is no special thing done other than to draw it. If a special draw function is needed one could be constructed. But in general Game no longer needs to call drawText() as if it was managing the screen instead of Terminal.
 +
 +== Creating a LP/TTY device
 +The first thing we need to do is create a lp-style output device. This will require the function putch and the helper functions cr and lf.
 +
 +Secondly we will need to add functions such as "delch" and "gotoxy".
  
pygame_terminal_ii.1699232001.txt.gz · Last modified: 2023/11/06 00:53 by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki