Terminal control/Positional read

From Rosetta Code
Revision as of 15:51, 5 November 2021 by Sos (talk | contribs)
Task
Terminal control/Positional read
You are encouraged to solve this task according to the task description, using any language you may know.

Determine the character displayed on the screen at column 3, row 6 and store that character in a variable. Note that it is permissible to utilize system or language provided methods or system provided facilities, system maintained records or available buffers or system maintained display records to achieve this task, rather than query the terminal directly, if those methods are more usual for the system type or language.

AutoHotkey

Works with: AutoHotkey_L

AutoHotkey is not built for the command line, so we need call the WinAPI directly.

For fun, this writes random characters to the command window so that it has something to retrieve.

<lang AHK>DllCall( "AllocConsole" ) ; create a console if not launched from one hConsole := DllCall( "GetStdHandle", int, STDOUT := -11 ) Loop 10 { Loop 10 { Random, asc, % asc("A"), % Asc("Z") WriteConsole(hConsole, Chr(asc)) } WriteConsole(hConsole, "`n") }

MsgBox % ReadConsoleOutputCharacter(hConsole, 1, 3, 6)

=== The below simply wraps part of the WinAPI ===

WriteConsole(hConsole, text){ VarSetCapacity(out, 16) If DllCall( "WriteConsole", UPtr, hConsole, Str, text, UInt, StrLen(text) , UPtrP, out, uint, 0 ) return out return 0 } ReadConsoleOutputCharacter(hConsole, length, x, y){ VarSetCapacity(out, length * (1 << !!A_IsUnicode)) VarSetCapacity(n, 16) if DllCall( "ReadConsoleOutputCharacter" , UPtr, hConsole , Str, out , UInt, length , UInt, x | (y << 16) , UPtrP, n )

&& VarSetCapacity(out, -1) return out return 0 }</lang>

BASIC

Applesoft BASIC

<lang ApplesoftBasic> 10 DEF FN C(H) = SCRN( H - 1,(V - 1) * 2) + SCRN( H - 1,(V - 1) * 2 + 1) * 16

20  LET V = 6:C$ =  CHR$ ( FN C(3))</lang>

Locomotive Basic

<lang locobasic>10 LOCATE 3,6 20 a$=COPYCHR$(#0)</lang>

Amstrad CPC screen memory only stores pixels but no character information (as opposed to e.g. the C64), so the firmware routine (TXT_UNWRITE) called by BASIC works by trying to find a match between screen pixels and the shape of a currently defined character. If the character table or screen pixels in the area of the character are changed between writing and reading, COPYCHR$ will therefore fail.

QBasic

The top left corner is (1, 1).

<lang qbasic>c$ = CHR$(SCREEN(6, 3))</lang>

ZX Spectrum Basic

<lang basic> 10 REM The top left corner is at position 0,0

20 REM So we subtract one from the coordinates
30 LET c$ = SCREEN$(5,2)</lang>

BBC BASIC

<lang bbcbasic> PRINT TAB(2,5) "Here"

     char$ = GET$(2,5)
     PRINT "Character at column 3 row 6 was " char$</lang>
Works with: all BBC BASICs
that support calling OSBYTE

<lang bbcbasic> PRINT TAB(2,5) "Here"

     PRINT TAB(2,5);                         : REM Position cursor over character to read
     A%=&87:char%=((USR&FFF4)AND&FF00)DIV256 : REM Ask operating system to read character
     PRINT "Character at column 3 row 6 was CHR$(";char%;")"</lang>

C

With the Windows console, call GetConsoleScreenBufferInfo() to find the top-left corner of the display screen. Then add (3, 6) to the top-left corner and call ReadConsoleOutputCharacterW() to read character. This program reckons that the top-left corner is (0, 0).

Library: Win32

<lang c>#include <windows.h>

  1. include <wchar.h>

int main() { CONSOLE_SCREEN_BUFFER_INFO info; COORD pos; HANDLE conout; long len; wchar_t c;

/* Create a handle to the console screen. */ conout = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (conout == INVALID_HANDLE_VALUE) return 1;

/* Where is the display window? */ if (GetConsoleScreenBufferInfo(conout, &info) == 0) return 1;

/* c = character at position. */ pos.X = info.srWindow.Left + 3; /* Column */ pos.Y = info.srWindow.Top + 6; /* Row */ if (ReadConsoleOutputCharacterW(conout, &c, 1, pos, &len) == 0 || len <= 0) return 1;

wprintf(L"Character at (3, 6) had been '%lc'\n", c); return 0; }</lang>

Common Lisp

ncurses

To interface the ncurses C library from Lisp, the croatoan library is used. <lang lisp>(defun positional-read ()

 (with-screen (scr :input-blocking t :input-echoing nil :cursor-visible nil)
   ;; print random characters in a 10x20 grid
   (loop for i from 0 to 9 do
        (loop for j from 0 to 19 do
             (add-char scr (+ 33 (random 94)) :y i :x j)))
   ;; highlight char to extract at row 6 column 3
   (change-attributes scr 1 (list :reverse) :y 5 :x 2)
   (refresh scr)
   ;; wait for keypress
   (get-char scr)
   ;; extract char from row 6 column 3
   (let ((char (extract-char scr :y 5 :x 2)))
     ;; then print it out again
     (move scr 11 0)
     (format scr "extracted char: ~A" char))
   (refresh scr)
   (get-char scr)))</lang>

Go

Translation of: Kotlin

<lang go>package main

/*

  1. include <windows.h>
  • /

import "C" import "fmt"

func main() {

   for i := 0; i < 80*25; i++ {
       fmt.Print("A")  // fill 80 x 25 console with 'A's
   }
   fmt.Println()
   conOut := C.GetStdHandle(C.STD_OUTPUT_HANDLE)
   info := C.CONSOLE_SCREEN_BUFFER_INFO{}
   pos := C.COORD{}
   C.GetConsoleScreenBufferInfo(conOut, &info)
   pos.X = info.srWindow.Left + 3 // column number 3 of display window
   pos.Y = info.srWindow.Top + 6  // row number 6 of display window
   var c C.wchar_t
   var le C.ulong
   ret := C.ReadConsoleOutputCharacterW(conOut, &c, 1, pos, &le)
   if ret == 0 || le <= 0 {
       fmt.Println("Something went wrong!")
       return
   }
   fmt.Printf("The character at column 3, row 6 is '%c'\n", c) 

}</lang>

Output:
The character at column 3, row 6 is 'A'

Julia

Translation of: Raku

<lang julia>using LibNCurses

randtxt(n) = foldl(*, rand(split("1234567890abcdefghijklmnopqrstuvwxyz", ""), n))

initscr()

for i in 1:20

   LibNCurses.mvwaddstr(i, 1, randtxt(50))

end

row = rand(1:20) col = rand(1:50) ch = LibNCurses.winch(row, col) LibNCurses.mvwaddstr(col, 52, "The character at ($row, $col) is $ch.") ) </lang>

Kotlin

This is based on the C entry and works on Windows 10: <lang scala>// Kotlin Native version 0.3

import kotlinx.cinterop.* import win32.*

fun main(args: Array<String>) {

   for (i in 0 until (80 * 25)) print("A")  // fill 80 x 25 console with 'A's
   println()
   memScoped {
       val conOut = GetStdHandle(-11)
       val info = alloc<CONSOLE_SCREEN_BUFFER_INFO>()       
       val pos = alloc<COORD>()
       GetConsoleScreenBufferInfo(conOut, info.ptr)
       pos.X = (info.srWindow.Left + 3).toShort()  // column number 3 of display window
       pos.Y = (info.srWindow.Top + 6).toShort()   // row number 6 of display window
       val c = alloc<wchar_tVar>()
       val len = alloc<IntVar>()
       ReadConsoleOutputCharacterW(conOut, c.ptr, 1, pos.readValue(), len.ptr)
       if (len.value == 1) {
           val ch = c.value.toChar()
           println("The character at column 3, row 6 is '$ch'")
       }
       else println("Something went wrong!")
   }   

}</lang>

Output:
The character at column 3, row 6 is 'A'

Ksh

<lang ksh>

  1. !/bin/ksh
  1. Determine the character displayed on the screen at column 3, row 6 and
  2. store that character in a variable.
  3. Use a group of functions "shellcurses"
  1. # Variables:

FPATH="/usr/local/functions/shellcurses/"

rst="�[0m" red="�[31m" whi="�[37m"

integer row=${1:-6} col=${2:-3} # Allow command line row col input

  1. # 10x10 grid of random digits

typeset -A grid for ((i=0; i<10; i++)); do for ((j=0; j<10; j++)); do (( grid[${i}][${j}] = (RANDOM % 9) + 1 )) done done

  1. # Functions:


######
  1. main #
######
  1. # Initialize the curses screen

initscr ; export MAX_LINES MAX_COLS

  1. # Display the random number grid with target in red

clear for ((i=1; i<=10; i++)); do for ((j=1; j<=10; j++)); do colr=${whi} (( i == row )) && (( j == col )) && colr=${red} mvaddstr ${i} ${j} "${colr}${grid[$((i-1))][$((j-1))]}${rst}" done done

str=$(rtnch ${row} ${col}) # return char at (row, col) location

mvaddstr 12 1 "Digit at (${row},${col}) = ${str}" # Display result move 14 1 refresh endwin</lang>

Output:

2466511215 6743931696 7546513188 7233775996 4748942123 8893655153 8341639139 8772676987 3772947552 8454526539

Digit at (6,3) = 9

Nim

Translation of: Raku
Translation of: Julia
Library: nim-ncurses

This Nim version is inspired by Raku and Julia versions. <lang Nim>import random, sequtils, strutils import ncurses

randomize()

let win = initscr() assert not win.isNil, "Unable to initialize."

for y in 0..9:

 mvaddstr(y.cint, 0, newSeqWith(10, sample({'0'..'9', 'a'..'z'})).join())

let row = rand(9).cint let col = rand(9).cint let ch = win.mvwinch(row, col)

mvaddstr(row, col + 11, "The character at ($1, $2) is $3.".format(row, col, chr(ch))) mvaddstr(11, 0, "Press any key to quit.") refresh() discard getch()

endwin()</lang>

Output:
tw5bl8mhvl
37t0nvwjhr
055co0b3hm
409stl3jgv         The character at (3, 8) is g.
a9j5354rci
o5ymlrtgt2
9yjag11u7n
2nv32g6s7x
3l1zujlpl2
opkl6us0mf

Press any key to quit.

Perl

Translation of: Raku

<lang Perl># 20200917 added Perl programming solution

use strict; use warnings;

use Curses;

initscr or die;

my $win = Curses->new;

foreach my $row (0..9) {

  $win->addstr( $row , 0, join(, map { chr(int(rand(50)) + 41) } (0..9)))

};

my $icol = 3 - 1; my $irow = 6 - 1;

my $ch = $win->inch($irow,$icol);

$win->addstr( $irow, $icol+10, 'Character at column 3, row 6 = '.$ch );

$win->addstr( LINES() - 2, 2, "Press any key to exit..." );

$win->getch;

endwin;</lang>

Phix

<lang Phix>-- -- demo\rosetta\Positional_read.exw -- ================================ -- position(6,1) -- line 6 column 1 (1-based) puts(1,"abcdef") integer {ch,attr} = get_screen_char(6,3) printf(1,"\n\n=>%c",ch) {} = wait_key()</lang>

Output:





abcdef

=>c

PowerShell

This gets the character at position (3, 6) of the buffer, not necessarily of the screen. <lang powershell> $coord = [System.Management.Automation.Host.Coordinates]::new(3, 6) $rect = [System.Management.Automation.Host.Rectangle]::new($coord, $coord) $char = $Host.UI.RawUI.GetBufferContents($rect).Character </lang>

Python

<lang python>import curses from random import randint


  1. Print random text in a 10x10 grid

stdscr = curses.initscr() for rows in range(10):

   line = .join([chr(randint(41, 90)) for i in range(10)])
   stdscr.addstr(line + '\n')
  1. Read

icol = 3 - 1 irow = 6 - 1 ch = stdscr.instr(irow, icol, 1).decode(encoding="utf-8")

  1. Show result

stdscr.move(irow, icol + 10) stdscr.addstr('Character at column 3, row 6 = ' + ch + '\n') stdscr.getch()

curses.endwin() </lang>

Output:
T@4;4G,XIJ
>C+PE0)RM;
JEV6B/8E?H
FSC>41UIGR
V>41JMXMOW
IY0*KH6M;B  Character at column 3, row 6 = 0
-6<UL*>DU7
MZ))<5D:B8
.@UB/P6UQ)
<9HYH)<ZJF

Racket

Works in a CMD box on Windows: <lang racket>

  1. lang racket

(require ffi/unsafe ffi/unsafe/define) (define-ffi-definer defwin #f) (defwin GetStdHandle (_fun _int -> _pointer)) (defwin ReadConsoleOutputCharacterA

 (_fun _pointer _pointer _uint _uint [len : (_ptr o _uint)] -> _bool))

(define b (make-bytes 1 32)) (and (ReadConsoleOutputCharacterA (GetStdHandle -11) b 1 #x50002)

    (printf "The character at 3x6 is <~a>\n" b))

</lang>

Raku

(formerly Perl 6)

Translation of: Python

<lang perl6>use NCurses;

  1. Reference:
  2. https://github.com/azawawi/perl6-ncurses
  1. Initialize curses window

my $win = initscr() or die "Failed to initialize ncurses\n";

  1. Print random text in a 10x10 grid

for ^10 { mvaddstr($_ , 0, (for ^10 {(41 .. 90).roll.chr}).join )};

  1. Read

my $icol = 3 - 1; my $irow = 6 - 1;

my $ch = mvinch($irow,$icol);

  1. Show result

mvaddstr($irow, $icol+10, 'Character at column 3, row 6 = ' ~ $ch.chr);

mvaddstr( LINES() - 2, 2, "Press any key to exit..." );

  1. Refresh (this is needed)

nc_refresh;

  1. Wait for a keypress

getch;

  1. Cleanup

LEAVE {

   delwin($win) if $win;
   endwin;

}</lang>

Output:
+W18:5I<1N
N-I.HG45SK
BFJY8:AK)8
J+4U<H1++:
RP>BX-/19Y
URDESVX;HX  Character at column 3, row 6 = D
J7+X3@E<BG
M;?2U<8+FI
)@BG,:D)O1
)>A-=LDY-.












  Press any key to exit...

REXX

The REXX doesn't have any cursor or screen management tools, but some REXX interpreters have added the functionality via different methods.

Works with: PC/REXX
Works with: Personal REXX

<lang rexx>/*REXX program demonstrates reading a character from (at) at specific screen location. */

   row = 6                                      /*point to a particular row   on screen*/
   col = 3                                      /*  "    " "     "      column "    "  */

howMany = 1 /*read this many characters from screen*/

stuff = scrRead(row, col, howMany) /*this'll do it. */

other = scrRead(40, 3, 1) /*same thing, but for row forty. */

                                                /*stick a fork in it,  we're all done. */</lang>

TXR

<lang txrlisp>;;; Type definitions and constants

(typedef BOOL (enum BOOL FALSE TRUE)) (typedef HANDLE cptr) (typedef WCHAR wchar) (typedef DWORD uint32) (typedef WORD uint16) (typedef SHORT short)

(typedef COORD

        (struct COORD
          (X SHORT)
          (Y SHORT)))

(typedef SMALL_RECT

        (struct SMALL_RECT
          (Left SHORT)
          (Top SHORT)
          (Right SHORT)
          (Bottom SHORT)))

(typedef CONSOLE_SCREEN_BUFFER_INFO

        (struct CONSOLE_SCREEN_BUFFER_INFO
          (dwSize COORD)
          (dwCursorPosition COORD)
          (wAttributes WORD)
          (srWindow SMALL_RECT)
          (dwMaximumWindowSize COORD)))
Various constants

(defvarl STD_INPUT_HANDLE (- #x100000000 10)) (defvarl STD_OUTPUT_HANDLE (- #x100000000 11)) (defvarl STD_ERROR_HANDLE (- #x100000000 12))

(defvarl NULL cptr-null) (defvarl INVALID_HANDLE_VALUE (cptr-int -1))

Foreign Function Bindings

(with-dyn-lib "kernel32.dll"

 (deffi GetStdHandle "GetStdHandle" HANDLE (DWORD))
 (deffi GetConsoleScreenBufferInfo "GetConsoleScreenBufferInfo"
        BOOL (HANDLE (ptr-out CONSOLE_SCREEN_BUFFER_INFO)))
 (deffi ReadConsoleOutputCharacter "ReadConsoleOutputCharacterW"
        BOOL (HANDLE (ptr-out (array 1 WCHAR))
                      DWORD COORD (ptr-out (array 1 DWORD)))))
Now the character at <2, 5> -- column 3, row 6.

(let ((console-handle (GetStdHandle STD_OUTPUT_HANDLE)))

 (when (equal console-handle INVALID_HANDLE_VALUE)
   (error "couldn't get console handle"))
 (let* ((cinfo (new CONSOLE_SCREEN_BUFFER_INFO))
        (getinfo-ok (GetConsoleScreenBufferInfo console-handle cinfo))
        (coord (if getinfo-ok
                 ^#S(COORD X ,(+ 2 cinfo.srWindow.Left)
                           Y ,(+ 5 cinfo.srWindow.Top))
                 #S(COORD X 0 Y 0)))
        (chars (vector 1))
        (nread (vector 1))
        (read-ok (ReadConsoleOutputCharacter console-handle chars
                                             1 coord nread)))
   (when (eq getinfo-ok 'FALSE)
     (error "GetConsoleScreenBufferInfo failed"))
   (prinl cinfo)
   (when (eq read-ok 'FALSE)
     (error "ReadConsoleOutputCharacter failed"))
   (unless (plusp [nread 0])
     (error "ReadConsoleOutputCharacter read zero characters"))
   (format t "character is ~s\n" [chars 0])))</lang>

Notes:

  • An ptr-out to an array of 1 DWORD is used for the number of characters out parameter. The FFI type (ptr-out DWORD) cannot work as a function argument, because integer objects are not mutable, and there isn't any concept of taking the address of a variable. A vector of 1 integer is mutable, and by making such a vector correspond with the FFI type (array 1 DWORD), the necessary semantics is achieved.
  • The quasiquote expression ^#S(COORD X ,(+ 2 cinfo.srWindow.Left) Y ,(+ 5 cinfo.srWindow.Top)) is equivalent to (new COORD X (+ 2 cinfo.srWindow.Left) Y (+ 5 cinfo.srWindow.Top)). It is done this way to demonstrate support for structure quasiquoting.

Wren

Translation of: Raku
Library: ncurses

An embedded program so we can ask the C host to communicate with ncurses for us. <lang ecmascript>/* terminal_control_positional_read.wren */

import "random" for Random

foreign class Window {

   construct initscr() {}
   foreign addstr(str)
   foreign inch(y, x)
   foreign move(y, x)
   foreign refresh()
   foreign getch()
   foreign delwin()

}

class Ncurses {

   foreign static endwin()

}

// initialize curses window var win = Window.initscr() if (win == 0) {

   System.print("Failed to initialize ncurses.")
   return

}

// print random text in a 10x10 grid var rand = Random.new() for (row in 0..9) {

   var line = (0..9).map{ |d| String.fromByte(rand.int(41, 91)) }.join()
   win.addstr(line + "\n")

}

// read var col = 3 - 1 var row = 6 - 1 var ch = win.inch(row, col)

// show result win.move(row, col + 10) win.addstr("Character at column 3, row 6 = %(ch)") win.move(11, 0) win.addstr("Press any key to exit...")

// refresh win.refresh()

// wait for a keypress win.getch()

// clean-up win.delwin() Ncurses.endwin()</lang>
We now embed this in the following C program, compile and run it. <lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. include <string.h>
  3. include <ncurses.h>
  4. include "wren.h"

/* C <=> Wren interface functions */

void C_windowAllocate(WrenVM* vm) {

   WINDOW** pwin = (WINDOW**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(WINDOW*));
   *pwin = initscr();

}

void C_addstr(WrenVM* vm) {

   WINDOW* win = *(WINDOW**)wrenGetSlotForeign(vm, 0);
   const char *str = wrenGetSlotString(vm, 1);
   waddstr(win, str);

}

void C_inch(WrenVM* vm) {

   WINDOW* win = *(WINDOW**)wrenGetSlotForeign(vm, 0);
   int y = (int)wrenGetSlotDouble(vm, 1);
   int x = (int)wrenGetSlotDouble(vm, 2);
   char c = (char)mvwinch(win, y, x);
   char s[2] = "\0";
   sprintf(s, "%c", c);
   wrenSetSlotString(vm, 0, s);

}

void C_move(WrenVM* vm) {

   WINDOW* win = *(WINDOW**)wrenGetSlotForeign(vm, 0);
   int y = (int)wrenGetSlotDouble(vm, 1);
   int x = (int)wrenGetSlotDouble(vm, 2);
   wmove(win, y, x);

}

void C_refresh(WrenVM* vm) {

   WINDOW* win = *(WINDOW**)wrenGetSlotForeign(vm, 0);
   wrefresh(win);

}

void C_getch(WrenVM* vm) {

   WINDOW* win = *(WINDOW**)wrenGetSlotForeign(vm, 0);
   wgetch(win);

}

void C_delwin(WrenVM* vm) {

   WINDOW* win = *(WINDOW**)wrenGetSlotForeign(vm, 0);
   delwin(win);

}

void C_endwin(WrenVM* vm) {

   endwin();

}

WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) {

   WrenForeignClassMethods methods;
   methods.allocate = NULL;
   methods.finalize = NULL;
   if (strcmp(module, "main") == 0) {
       if (strcmp(className, "Window") == 0) {
           methods.allocate = C_windowAllocate;
       }
   }
   return methods;

}

WrenForeignMethodFn bindForeignMethod(

   WrenVM* vm,
   const char* module,
   const char* className,
   bool isStatic,
   const char* signature) {
   if (strcmp(module, "main") == 0) {
       if (strcmp(className, "Window") == 0) {
           if (!isStatic && strcmp(signature, "addstr(_)") == 0) return C_addstr;
           if (!isStatic && strcmp(signature, "inch(_,_)") == 0) return C_inch;
           if (!isStatic && strcmp(signature, "move(_,_)") == 0) return C_move;
           if (!isStatic && strcmp(signature, "refresh()") == 0) return C_refresh;
           if (!isStatic && strcmp(signature, "getch()") == 0)   return C_getch;
           if (!isStatic && strcmp(signature, "delwin()") == 0)  return C_delwin;
       } else if (strcmp(className, "Ncurses") == 0) {
           if ( isStatic && strcmp(signature, "endwin()") == 0)  return C_endwin;
       }
   }
   return NULL;

}

static void writeFn(WrenVM* vm, const char* text) {

   printf("%s", text);

}

void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {

   switch (errorType) {
       case WREN_ERROR_COMPILE:
           printf("[%s line %d] [Error] %s\n", module, line, msg);
           break;
       case WREN_ERROR_STACK_TRACE:
           printf("[%s line %d] in %s\n", module, line, msg);
           break;
       case WREN_ERROR_RUNTIME:
           printf("[Runtime Error] %s\n", msg);
           break;
   }

}

char *readFile(const char *fileName) {

   FILE *f = fopen(fileName, "r");
   fseek(f, 0, SEEK_END);
   long fsize = ftell(f);
   rewind(f);
   char *script = malloc(fsize + 1);
   fread(script, 1, fsize, f);
   fclose(f);
   script[fsize] = 0;
   return script;

}

int main(int argc, char **argv) {

   WrenConfiguration config;
   wrenInitConfiguration(&config);
   config.writeFn = &writeFn;
   config.errorFn = &errorFn;
   config.bindForeignClassFn = &bindForeignClass;
   config.bindForeignMethodFn = &bindForeignMethod;
   WrenVM* vm = wrenNewVM(&config);
   const char* module = "main";
   const char* fileName = "terminal_control_positional_read.wren";
   char *script = readFile(fileName);
   WrenInterpretResult result = wrenInterpret(vm, module, script);
   switch (result) {
       case WREN_RESULT_COMPILE_ERROR:
           printf("Compile Error!\n");
           break;
       case WREN_RESULT_RUNTIME_ERROR:
           printf("Runtime Error!\n");
           break;
       case WREN_RESULT_SUCCESS:
           break;
   }
   wrenFreeVM(vm);
   free(script);
   return 0;

}</lang>

Output:

Sample output:

86.Z=B>)0I
R/X,HX=6RE
>*12?I-G0+
D*8S-2A.)3
9)+=89UNXW
YQN4L8NC4W  Character at column 3, row 6 = N
6EQC@=))B/
XWOSZ4/CR@
LU->=2@RW1
ZKFRC9EOT0

Press any key to exit...

XPL0

<lang XPL0>include c:\cxpl\stdlib; int C; [Cursor(3, 6); \move cursor to column 3, row 6 (top left = 0,0) \Call BIOS interrupt routine to read character (& attribute) at cursor position C:= CallInt($10, $0800, 0) & $00FF; \mask off attribute, leaving the character ]</lang>