I just finished my initial work on a VGA Console emulator for Pygame. This is great, if say you need to add a retro style VGA Console into your game, or if you want to build an application that takes full advantage of the VGA video adapter. At the moment, I am releasing it as a Preview Demo, and it isn't ready for any production applications or games just yet. However, what is available in this preview is plentiful. You can download the full source from my Bitbucket page, which is linked in the right hand column of my blog. I will also include the source here and try to explain what it can currently so. I have also enclosed a screenshot of the demo app both in this blog post and in the bitbucket repository for those too lazy to download it and try it themselves in Pygame.
import pygame, sys
from pygame.locals import *
import mmap
clock = pygame.time.Clock()
class VGAConsole(object):
VGA_PALETTE = (
(0,0,0),
(0,0,168),
(0,168,0),
(0,168,168),
(168,0,0),
(168,0,168),
(168,87,0),
(168,168,168),
(87,87,87),
(87,87,255),
(87,255,87),
(87,255,255),
(255,87,87),
(255,87,255),
(255,255,87),
(255,255,255),
)
US_SHIFTMAP = {
49: 33,
50: 64,
51: 35,
52: 36,
53: 37,
54: 94,
55: 38,
56: 42,
57: 40,
48: 41,
96: 126,
45: 95,
61: 43,
91: 123,
93: 125,
59: 58,
39: 34,
92: 124,
44: 60,
46: 62,
47: 63,
}
def __init__(self):
self.vgabuf = mmap.mmap(-1, 4000)
pygame.display.init()
pygame.font.init()
self.screen = pygame.display.set_mode((640,400),0,8)
pygame.display.set_caption('VGA Console test')
self.font = pygame.font.Font('VGA.ttf', 16)
self.cursor = self.font.render(chr(219),0,self.VGA_PALETTE[7])
pygame.mouse.set_visible(False)
self.pos = [0,0]
self.background = self.VGA_PALETTE[1]
self.shift = False
self.cframe = 0
self.cframes = []
for c in ('|', '/', '-', '\\',):
self.cframes.append(self.font.render(c,0,self.VGA_PALETTE[15],self.background))
def draw(self):
self.screen.fill(self.background)
self.vgabuf.seek(0)
for y in range(0,25):
for x in range(0,80):
attr = ord(self.vgabuf.read_byte())
fg,bg = 7,0
if attr > 0:
fg,bg = attr&0xf, (attr&0xf0)>>4
c = self.vgabuf.read_byte()
if ord(c) > 0:
self.screen.blit(self.font.render(c,0,self.VGA_PALETTE[fg],self.VGA_PALETTE[bg]), (x*8,y*16))
self.drawMouse()
self.drawCursor()
pygame.display.update()
def setXY(self, row, col, c):
self.vgabuf.seek((80*row+col)*2)
self.vgabuf.write(chr(0x1f)+chr(c))
def type(self, c):
if c == 13:
self.pos[1] = 0
self.pos[0] +=1
elif c == 8:
if self.pos[1] > 0:
self.pos[1] -=1
self.setXY(self.pos[0], self.pos[1], 0)
elif c == 9:
self.pos[1] += 8
elif c == 27:
pygame.quit()
sys.exit()
else:
self.setXY(self.pos[0], self.pos[1], c)
self.pos[1] +=1
if self.pos[1] > 80:
self.pos[1] = 0
self.pos[0] += 1
def write(self, text):
for c in text:
self.type(ord(c))
def draw_ascii(self):
row, col = 10,10
for c in range(0,255):
self.setXY(row,col,c)
col +=1
if col > 41:
col = 10
row+=1
def draw_window(self, row, col, height, width, title=None):
self.setPos(row, col)
brd = chr(205)*(width-1)
self.write(chr(213)+brd+chr(184))
for y in range(row+1, row+height):
self.setXY(y, col, 179)
self.setXY(y, col+width, 179)
self.setPos(row+height, col)
self.write(chr(212)+brd+chr(190))
if title:
self.setPos(row, col+((width/2)-len(title)/2))
self.write(title)
def clear_window(self, row, col, height, width):
for y in range(row, row+height+1):
self.setPos(y, col)
self.write(chr(0)*(width+1))
def setPos(self, row, col):
self.pos = [row, col]
def clearScreen(self):
self.vgabuf.seek(0)
self.vgabuf.write(chr(0)*4000)
self.setPos(0, 0)
def mousePos(self):
x,y = pygame.mouse.get_pos()
return (y/16, x/8)
def drawMouse(self):
row,col = self.mousePos()
self.screen.blit(self.cursor, (col*8,row*16))
def drawCursor(self):
self.screen.blit(self.cframes[self.cframe/3%4], (self.pos[1]*8,self.pos[0]*16))
self.cframe+=1
def main(self):
self.draw_ascii()
self.draw_window(9,9,9,33, ' ASCII ')
self.setPos(0, 0)
self.write('Welcome to VGAConsole!\rC:\>')
self.draw()
#pygame.event.set_blocked(MOUSEMOTION)
while 1:
clock.tick(30)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONUP:
oldpos = self.pos
self.clear_window(9, 9, 9, 33)
self.pos = oldpos
elif event.type == KEYDOWN:
if event.key == K_LSHIFT or event.key == K_RSHIFT:
self.shift = True
if event.key > 0 and event.key < 256:
c = event.key
if self.shift:
if c > 96 and c < 123:
c-=32
elif c in self.US_SHIFTMAP.keys():
c = self.US_SHIFTMAP[c]
self.type(c)
elif event.type == KEYUP:
if event.key == K_LSHIFT or event.key == K_RSHIFT:
self.shift = False
self.draw()
VGAConsole().main()
As you can see from the screenshot, it's very complete looking, with full support for all those ASCII characters you knew and loved. The display itself is rendered from the same format as the VGA memory, where each character on screen takes 2 bytes. One byte stores some attribute information, such as the foreground and background colors, and the other byte is the ASCII character code. In the actual VGA memory, the attribute also supported a blink flag, which I omitted in this VGA Console, as it's annoying and I really don't want to see it used... So, rather than supporting blink, it supports a full 16 colors for both the foreground and the background. In the original VGA memory, it only supported 8-color backgrounds. Since the memory format for VGA is the same as located at &0xB800 memory segment on real hardware, you can technically use a memory dump file created by say BSAVE commands. I may enable native BSAVEing and BLOADing of the memory buffer in future version.
During the draw() function, the VGA memory space is read and a surface is blitted. Currently, this surface is a display Surface object, but in the next release this will change to render to a normal Surface so that it can be blitted by the developer onto whatever Surface or display they want. This may cause issues with the mouse emulation however, I will need to look into that and see how the Pygame mouse works with Surfaces. So, yes, mouse emulation is also present. It displays a familiar block cursor we all used to remember. There is an API to grab it's location on the text display, so that you can determine if a text object or widget has been clicked.
One of the major features which is missing is input and output buffering support. At the moment, the current API allows you to write directly to the VGA memory buffer to draw text onto the console. This isn't very ideal for several reasons. It will not work correctly with stdin/stdout applications, and taking input into a string isn't currently possible. Typing onto the console is currently possible, but the input is not buffered, and the user may edit anything on the screen. Proper stdin and stdout is supported in the next release. This will enable many standard Python functions which require either or both to actually work on the VGA console. This will really open the doors to what will be possible with VGA Console.