Improve the console emulation a bit, can now run little brick out (but the gameplay...
authorNick Downing <nick@ndcode.org>
Mon, 23 May 2022 23:34:02 +0000 (09:34 +1000)
committerNick Downing <nick@ndcode.org>
Tue, 24 May 2022 02:45:30 +0000 (12:45 +1000)
apple_io.py
test_kb.bas [new file with mode: 0644]

index 22e8089..3251540 100644 (file)
@@ -203,6 +203,7 @@ gr_width = 1
 termios_attr = None
 fd_in = sys.stdin.fileno()
 fd_out = sys.stdout.fileno()
+ch_in = ''
 poll_in = select.poll()
 poll_in.register(fd_in, select.POLLIN)
 mem = {
@@ -229,7 +230,7 @@ mem = {
   HW_PB1: 0,
   HW_PB2: 0,
 }
-gr_mem = [[0 for j in range(40)] for i in range(40)]
+gr_mem = [[0 for j in range(40)] for i in range(48)]
 pdl_value = [255 for i in range(4)]
 
 def init():
@@ -262,9 +263,25 @@ def pr_hash(n):
 def in_hash(n):
   pass
 
+# don't use Python buffered I/O facility for stdin, because we don't know of
+# a documented way to check the input buffer level, and without this we can't
+# meaningfully use poll() to check whether an input character is available
 def read(n):
-  return str(os.read(fd_in, 1), 'utf-8')
-
+  out = []
+  while len(out) < n:
+    # decode utf-8 character
+    _in = os.read(fd_in, 1)
+    if _in[0] & 0xe0 == 0xc0:
+      _in += os.read(fd__in, 1)
+    elif _in[0] & 0xf0 == 0xe0:
+      _in += os.read(fd_in, 2)
+    elif _in[0] & 0xf8 == 0xf0:
+      _in += os.read(fd_in, 3)
+    out.append(str(_in, 'utf-8'))
+  return ''.join(out)
+
+# don't use Python buffered I/O facility for stdout, because applesoft code
+# uses plot/print for animation, so we'd need to flush it every time anyway
 def write(data):
   os.write(fd_out, bytes(data, 'utf-8'))
 
@@ -294,23 +311,38 @@ def _print(data):
       # apple treats \r as \r\n, so if you write e.g. \r\n you'll get \r\n\n
       crlf()
     elif ord(ch) >= 0x20:
+      # some applications expect BASL, BASH = base address of current line
+      addr = bascalc(mem[ZP_CV])
+      mem[ZP_BASL] = addr & 0xff
+      mem[ZP_BASH] = addr >> 8
+      mem[addr + mem[ZP_CH]] = (ord(ch) & 0x7f) | 0x80
+
       write(ch)
       mem[ZP_CH] += 1
       if mem[ZP_CH] >= mem[ZP_WNDLFT] + mem[ZP_WNDWTH]:
         crlf()
 
-def get():
-  # if there is a lookahead from PEEK of HW_IOADR, cancel and return it
-  if mem[HW_IOADR] >= 0x80:
-    mem[HW_IOADR] &= 0x7f
-    return chr(HW_IOADR)
-  ch = read(1) 
-  if len(ch) == 0:
+def get_internal():
+  global ch_in
+
+  ch_in = read(1)
+  if len(ch_in) == 0:
     raise Exception('end of input') # due to piping or input redirection
-  if ch == '\x03':
+  if ch_in == '\x03':
     raise Exception('user break')
-  if ch == '\x7f':
-    ch = '\b'
+  if ch_in == '\x7f':
+    ch_in = '\b'
+  mem[HW_IOADR] = (ord(ch_in) & 0x7f) | 0x80
+  return
+
+def get():
+  global ch_in
+
+  if len(ch_in) == 0:
+    get_internal()
+  ch = ch_in
+  ch_in = ''
+  mem[HW_IOADR] &= 0x7f
   return ch
 
 def input():
@@ -346,6 +378,9 @@ def vtab(y):
   write(f'\x1b[{y:d}d')
   mem[ZP_CV] = y - 1
 
+def cleol():
+  write('\x1b[K')
+
 def clreop():
   write('\x1b[J')
 
@@ -404,17 +439,25 @@ def lomem(addr):
 def peek(addr):
   addr &= 0xffff
   if addr == HW_IOADR:
-    if mem[HW_IOADR] < 0x80 and len(poll_in.poll(POLL_TIMEOUT_MS)):
-      mem[HW_IOADR] = ord(read(1)) | 0x80
+    # sometimes the application ignores an invalid key and waits for a new one
+    #if len(ch_in) == 0 and len(poll_in.poll(POLL_TIMEOUT_MS)):
+    if len(poll_in.poll(POLL_TIMEOUT_MS)):
+      get_internal()
   return mem.get(addr, 0)
 
 def poke(addr, data):
+  global ch_in
+
   addr &= 0xffff
-  data &= 0xff
-  mem[addr] = data
-  if addr == ZP_WNDTOP or addr == ZP_WNDBTM:
-    # save cursor, set scrolling region, restore cursor
-    write(f'\x1b[s\x1b[{mem[ZP_WNDTOP] + 1:d};{mem[ZP_WNDBTM]:d}r\x1b[u')
+  if addr == HW_KBDSTRB:
+    ch_in = ''
+    mem[HW_IOADR] &= 0x7f
+  else:
+    data &= 0xff
+    mem[addr] = data
+    if addr == ZP_WNDTOP or addr == ZP_WNDBTM:
+      # save cursor, set scrolling region, restore cursor
+      write(f'\x1b[s\x1b[{mem[ZP_WNDTOP] + 1:d};{mem[ZP_WNDBTM]:d}r\x1b[u')
 
 def call(addr):
   global flash_color0
@@ -475,9 +518,16 @@ def call(addr):
     clreop()
   elif addr == ROM_BELL2:
     tone(1000, 100) # 1 kHz for .1 sec
+  elif addr == ROM_HOME:
+    home()
+  elif addr == ROM_CLEOL:
+    cleol()
   else:
     raise Exception(f'call {addr:04x}')
 
+def bascalc(y):
+  return 0x400 | ((y & 7) << 7) | ((y >> 3) * 40)
+
 def text():
   # save cursor, set scrolling region, restore cursor
   write('\x1b[s\x1b1;24r\x1b[u')
diff --git a/test_kb.bas b/test_kb.bas
new file mode 100644 (file)
index 0000000..89a1f6a
--- /dev/null
@@ -0,0 +1,2 @@
+10IFPEEK(-16384)>=128THENPRINTPEEK(-16384)-128:POKE-16368,0
+20GOTO10