Implement VIEW/WINDOW/PALETTE, PMAP, fix MBF float format, update to v0.13.0
Graphics viewport and coordinate mapping: - VIEW [[SCREEN] (x1,y1)-(x2,y2) [,[fill][,border]]] with clipping - WINDOW [[SCREEN] (x1,y1)-(x2,y2)] with Cartesian/screen modes - PALETTE [attribute, color] with CGA 16-color remapping - PMAP(coord, func) for logical/physical coordinate conversion - All graphics statements (PSET, LINE, CIRCLE, PAINT, GET/PUT) respect viewport clipping and WINDOW coordinate mapping MBF (Microsoft Binary Format) float support: - CVS/CVD now interpret bytes as MBF format (compatible with real GW-BASIC) - MKS$/MKD$ now produce MBF-encoded bytes - Fixed shift errors in MBF↔IEEE conversion routines (single: 1→0, double: 4→3) - Random-access file I/O now byte-compatible with original GWBASIC.EXE 66 tests (2 new), 61 compat matches (up from 58).
This commit is contained in:
@@ -23,7 +23,7 @@ Interactive mode launches the authentic GW-BASIC full-screen editor:
|
||||
|
||||
```
|
||||
$ ./gwbasic
|
||||
GW-BASIC 2026 0.12.0
|
||||
GW-BASIC 2026 0.13.0
|
||||
(C) Eremey Valetov 2026. MIT License.
|
||||
Based on Microsoft GW-BASIC assembly source.
|
||||
Ok
|
||||
@@ -77,7 +77,7 @@ SPACE$, STRING$, HEX$, OCT$, INSTR, INPUT$
|
||||
| File management | KILL, NAME, FILES, MKDIR, RMDIR, CHDIR, SHELL |
|
||||
| Date/time | DATE$, TIME$, TIMER |
|
||||
| Screen | LOCATE, COLOR, WIDTH, SCREEN, KEY ON/OFF/LIST |
|
||||
| Graphics | PSET, PRESET, LINE, CIRCLE, DRAW, PAINT, GET/PUT (sprites), BSAVE, BLOAD |
|
||||
| Graphics | PSET, PRESET, LINE, CIRCLE, DRAW, PAINT, GET/PUT (sprites), BSAVE, BLOAD, VIEW, WINDOW, PALETTE, PMAP |
|
||||
| Sound | SOUND, BEEP, PLAY (MML) |
|
||||
| Memory | DEF SEG, PEEK, POKE |
|
||||
|
||||
@@ -206,7 +206,7 @@ Source text → Tokenizer (CRUNCH) → Token stream
|
||||
| Platform | hal_posix.c | OEM*.ASM |
|
||||
|
||||
Key design differences from the original:
|
||||
- IEEE 754 floating point (MBF conversion only for CVI/CVS/CVD compatibility)
|
||||
- IEEE 754 floating point (MBF conversion for CVI/CVS/CVD/MKI$/MKS$/MKD$ file compatibility)
|
||||
- Dynamic memory allocation instead of 64KB segment layout
|
||||
- malloc'd strings instead of compacting garbage collector
|
||||
- setjmp/longjmp for error recovery
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
| 0.10.0 | | Binary tokenized SAVE/LOAD, INKEY$ extended key sequences, golden-file regression tests, classic BASIC programs |
|
||||
| 0.11.0 | | DEF SEG / PEEK / POKE virtual memory, GET/PUT graphics sprites, PRINT USING fixes |
|
||||
| 0.12.0 | | BSAVE/BLOAD, TUI ANSI 16-color rendering, CGA graphics framebuffer PEEK/POKE, keyboard shift flags |
|
||||
| 0.13.0 | | VIEW/WINDOW/PALETTE graphics, PMAP function, MBF float format for CVS/CVD/MKS$/MKD$ |
|
||||
|
||||
## Tests
|
||||
|
||||
64 test programs in `tests/programs/`, plus 4 classic interactive programs in
|
||||
66 test programs in `tests/programs/`, plus 4 classic interactive programs in
|
||||
`tests/classic/` (Hamurabi, Lunar Lander, Gunner, Diamond from David Ahl's
|
||||
*BASIC Computer Games*).
|
||||
|
||||
@@ -29,7 +30,7 @@ Run the full automated suite:
|
||||
bash tests/run_tests.sh
|
||||
```
|
||||
|
||||
Each test has a 5-second timeout. 60 tests have `.expected` golden files
|
||||
Each test has a 5-second timeout. 62 tests have `.expected` golden files
|
||||
for output regression detection. Three timing-dependent tests (datetime,
|
||||
on_timer, timer_stop) and one visual test (color_test) run without golden
|
||||
comparison.
|
||||
|
||||
@@ -57,7 +57,7 @@ type suffixes (`%`, `!`, `#`)
|
||||
| File management | `KILL`, `NAME`, `FILES`, `MKDIR`, `RMDIR`, `CHDIR`, `SHELL` |
|
||||
| Date/time | `DATE$`, `TIME$`, `TIMER` |
|
||||
| Screen | `LOCATE`, `COLOR`, `WIDTH`, `SCREEN`, `KEY ON`/`OFF`/`LIST`, `KEY n,"string"` |
|
||||
| Graphics | `PSET`, `PRESET`, `LINE`, `CIRCLE`, `DRAW`, `PAINT`, `GET`, `PUT` |
|
||||
| Graphics | `PSET`, `PRESET`, `LINE`, `CIRCLE`, `DRAW`, `PAINT`, `GET`, `PUT`, `VIEW`, `WINDOW`, `PALETTE`, `PMAP` |
|
||||
| Sound | `SOUND`, `BEEP`, `PLAY` (MML parser, PulseAudio backend) |
|
||||
| Memory | `DEF SEG`, `PEEK`, `POKE`, `BSAVE`, `BLOAD` |
|
||||
| Misc | `KEY`, `TRON`/`TROFF`, `OPTION BASE`, `MID$` assignment |
|
||||
@@ -158,6 +158,20 @@ Sprite data is stored in CGA-compatible packed format: word 0 is the width
|
||||
in bits, word 1 is the height, and remaining words contain packed pixel data
|
||||
matching the original GW-BASIC representation.
|
||||
|
||||
### VIEW / WINDOW / PALETTE
|
||||
|
||||
- `VIEW [[SCREEN] (x1,y1)-(x2,y2) [,[fill][,border]]]` — define a graphics
|
||||
viewport. Without `SCREEN`, drawing coordinates are relative to the
|
||||
viewport origin. With `SCREEN`, coordinates remain absolute. Without
|
||||
arguments, resets to full screen.
|
||||
- `WINDOW [[SCREEN] (x1,y1)-(x2,y2)]` — map logical coordinates onto the
|
||||
viewport. Without `SCREEN`, Y increases upward (Cartesian); with `SCREEN`,
|
||||
Y increases downward. Without arguments, resets to physical coordinates.
|
||||
- `PALETTE [attribute, color]` — remap a color attribute to a different
|
||||
physical color (0–15). Without arguments, resets to the default CGA palette.
|
||||
- `PMAP(coordinate, function)` — convert between logical and physical
|
||||
coordinates. Function 0/1 = logical→physical X/Y; 2/3 = physical→logical X/Y.
|
||||
|
||||
### Example
|
||||
|
||||
```
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
bus so retro programs that talk to the speaker, joystick port, or parallel
|
||||
interface can do *something* useful instead of silently no-oping. Think of it
|
||||
as a tiny museum exhibit for vintage BASIC hardware hacking.
|
||||
- **VIEW / WINDOW / PALETTE** — graphics viewport, coordinate mapping, and
|
||||
palette remapping
|
||||
- **MBF format support for binary LOAD** — convert Microsoft Binary Format
|
||||
float constants when loading files saved by the original GWBASIC.EXE
|
||||
|
||||
@@ -36,8 +34,6 @@
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- Binary files use IEEE 754 floats, not MBF — files saved here are not
|
||||
byte-compatible with original GWBASIC.EXE binary format
|
||||
- String garbage collection not implemented (uses `malloc`/`free` instead)
|
||||
- Maximum 256 variables, 64 arrays, 16 FOR nesting, 24 GOSUB nesting,
|
||||
16 WHILE nesting
|
||||
|
||||
@@ -50,6 +50,21 @@ void gfx_sprite_put(int x, int y, const int16_t *buf, int bufsize, int action);
|
||||
/* Calculate required array element count for a sprite rectangle. */
|
||||
int gfx_sprite_size(int x1, int y1, int x2, int y2);
|
||||
|
||||
/* VIEW / WINDOW / PALETTE */
|
||||
void gfx_view(bool screen_flag, int x1, int y1, int x2, int y2,
|
||||
int fill, bool has_fill, int border, bool has_border);
|
||||
void gfx_view_reset(void);
|
||||
void gfx_window(bool screen_flag, double x1, double y1, double x2, double y2);
|
||||
void gfx_window_reset(void);
|
||||
void gfx_palette_set(int attr, int color);
|
||||
void gfx_palette_reset(void);
|
||||
|
||||
/* Coordinate mapping (logical ↔ physical) */
|
||||
int gfx_map_x(double x);
|
||||
int gfx_map_y(double y);
|
||||
double gfx_pmap(double coord, int func);
|
||||
bool gfx_has_window(void);
|
||||
|
||||
/* CGA framebuffer PEEK/POKE (segment 0xB800 in graphics mode) */
|
||||
uint8_t gfx_cga_peek(uint16_t offset);
|
||||
void gfx_cga_poke(uint16_t offset, uint8_t val);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "gw_math.h"
|
||||
#include "strings.h"
|
||||
|
||||
#define GW_VERSION "0.12.0"
|
||||
#define GW_VERSION "0.13.0"
|
||||
#define GW_BANNER "GW-BASIC " GW_VERSION
|
||||
|
||||
/* Tokenizer */
|
||||
|
||||
12
src/eval.c
12
src/eval.c
@@ -987,6 +987,18 @@ static gw_value_t eval_atom(void)
|
||||
v.fval = (float)(tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec);
|
||||
return v;
|
||||
}
|
||||
if (xtok == XSTMT_PMAP) {
|
||||
gw_chrget();
|
||||
gw_expect('(');
|
||||
gw_value_t arg = gw_eval_num();
|
||||
gw_expect(',');
|
||||
int func = gw_eval_int();
|
||||
gw_expect_rparen();
|
||||
gw_value_t v;
|
||||
v.type = VT_SNG;
|
||||
v.fval = (float)gfx_pmap(gw_to_dbl(&arg), func);
|
||||
return v;
|
||||
}
|
||||
gw.text_ptr = save;
|
||||
}
|
||||
|
||||
|
||||
18
src/fileio.c
18
src/fileio.c
@@ -733,10 +733,12 @@ gw_value_t gw_fn_cvs(gw_value_t *s)
|
||||
{
|
||||
if (s->type != VT_STR) gw_error(ERR_TM);
|
||||
if (s->sval.len < 4) gw_error(ERR_FC);
|
||||
mbf_single_t mbf;
|
||||
memcpy(&mbf, s->sval.data, 4);
|
||||
gw_str_free(&s->sval);
|
||||
gw_value_t v;
|
||||
v.type = VT_SNG;
|
||||
memcpy(&v.fval, s->sval.data, 4);
|
||||
gw_str_free(&s->sval);
|
||||
v.fval = gw_mbf_to_ieee_single(mbf);
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -744,10 +746,12 @@ gw_value_t gw_fn_cvd(gw_value_t *s)
|
||||
{
|
||||
if (s->type != VT_STR) gw_error(ERR_TM);
|
||||
if (s->sval.len < 8) gw_error(ERR_FC);
|
||||
mbf_double_t mbf;
|
||||
memcpy(&mbf, s->sval.data, 8);
|
||||
gw_str_free(&s->sval);
|
||||
gw_value_t v;
|
||||
v.type = VT_DBL;
|
||||
memcpy(&v.dval, s->sval.data, 8);
|
||||
gw_str_free(&s->sval);
|
||||
v.dval = gw_mbf_to_ieee_double(mbf);
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -763,18 +767,20 @@ gw_value_t gw_fn_mki(int16_t n)
|
||||
|
||||
gw_value_t gw_fn_mks(float f)
|
||||
{
|
||||
mbf_single_t mbf = gw_ieee_to_mbf_single(f);
|
||||
gw_value_t v;
|
||||
v.type = VT_STR;
|
||||
v.sval = gw_str_alloc(4);
|
||||
memcpy(v.sval.data, &f, 4);
|
||||
memcpy(v.sval.data, &mbf, 4);
|
||||
return v;
|
||||
}
|
||||
|
||||
gw_value_t gw_fn_mkd(double d)
|
||||
{
|
||||
mbf_double_t mbf = gw_ieee_to_mbf_double(d);
|
||||
gw_value_t v;
|
||||
v.type = VT_STR;
|
||||
v.sval = gw_str_alloc(8);
|
||||
memcpy(v.sval.data, &d, 8);
|
||||
memcpy(v.sval.data, &mbf, 8);
|
||||
return v;
|
||||
}
|
||||
|
||||
219
src/graphics.c
219
src/graphics.c
@@ -13,13 +13,35 @@ static int current_color = 1;
|
||||
static int last_x, last_y;
|
||||
|
||||
/* CGA default palette (RGBI) */
|
||||
static uint32_t palette[16] = {
|
||||
static const uint32_t default_palette[16] = {
|
||||
0x000000, 0x0000AA, 0x00AA00, 0x00AAAA,
|
||||
0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA,
|
||||
0x555555, 0x5555FF, 0x55FF55, 0x55FFFF,
|
||||
0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF,
|
||||
};
|
||||
|
||||
/* Active palette: palette_map[attr] gives the physical color index */
|
||||
static int palette_map[16];
|
||||
static uint32_t palette[16];
|
||||
|
||||
/* Viewport state (VIEW) */
|
||||
static bool vp_active;
|
||||
static bool vp_screen; /* VIEW SCREEN flag */
|
||||
static int vp_x1, vp_y1, vp_x2, vp_y2;
|
||||
|
||||
/* Window state (WINDOW) */
|
||||
static bool win_active;
|
||||
static bool win_screen; /* WINDOW SCREEN flag */
|
||||
static double win_x1, win_y1, win_x2, win_y2;
|
||||
|
||||
static void reset_palette(void)
|
||||
{
|
||||
for (int i = 0; i < 16; i++) {
|
||||
palette_map[i] = i;
|
||||
palette[i] = default_palette[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool gfx_active(void) { return framebuf != NULL; }
|
||||
int gfx_get_mode(void) { return screen_mode; }
|
||||
void gfx_set_color(int c) { current_color = c; }
|
||||
@@ -42,6 +64,11 @@ void gfx_init(int mode)
|
||||
current_color = (mode == 2) ? 1 : 3;
|
||||
last_x = 0;
|
||||
last_y = 0;
|
||||
vp_active = false;
|
||||
vp_x1 = 0; vp_y1 = 0;
|
||||
vp_x2 = fb_width - 1; vp_y2 = fb_height - 1;
|
||||
win_active = false;
|
||||
reset_palette();
|
||||
}
|
||||
|
||||
void gfx_shutdown(void)
|
||||
@@ -51,17 +78,40 @@ void gfx_shutdown(void)
|
||||
fb_width = 0;
|
||||
fb_height = 0;
|
||||
screen_mode = 0;
|
||||
vp_active = false;
|
||||
win_active = false;
|
||||
reset_palette();
|
||||
}
|
||||
|
||||
void gfx_cls(void)
|
||||
{
|
||||
if (framebuf)
|
||||
if (!framebuf) return;
|
||||
if (vp_active) {
|
||||
for (int y = vp_y1; y <= vp_y2; y++)
|
||||
for (int x = vp_x1; x <= vp_x2; x++)
|
||||
framebuf[y * fb_width + x] = 0;
|
||||
} else {
|
||||
memset(framebuf, 0, fb_width * fb_height);
|
||||
}
|
||||
}
|
||||
|
||||
/* Clip bounds: viewport when active, otherwise full framebuffer */
|
||||
static inline void clip_bounds(int *cx1, int *cy1, int *cx2, int *cy2)
|
||||
{
|
||||
if (vp_active) {
|
||||
*cx1 = vp_x1; *cy1 = vp_y1;
|
||||
*cx2 = vp_x2; *cy2 = vp_y2;
|
||||
} else {
|
||||
*cx1 = 0; *cy1 = 0;
|
||||
*cx2 = fb_width - 1; *cy2 = fb_height - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void set_pixel(int x, int y, int color)
|
||||
{
|
||||
if (x >= 0 && x < fb_width && y >= 0 && y < fb_height)
|
||||
int cx1, cy1, cx2, cy2;
|
||||
clip_bounds(&cx1, &cy1, &cx2, &cy2);
|
||||
if (x >= cx1 && x <= cx2 && y >= cy1 && y <= cy2)
|
||||
framebuf[y * fb_width + x] = color;
|
||||
}
|
||||
|
||||
@@ -350,10 +400,10 @@ void gfx_flush(void)
|
||||
/* DCS q - enter Sixel mode */
|
||||
EMIT("\033Pq");
|
||||
|
||||
/* Define palette */
|
||||
/* Define palette (using palette mapping) */
|
||||
for (int c = 0; c < 16; c++) {
|
||||
if (!color_used[c]) continue;
|
||||
uint32_t rgb = palette[c];
|
||||
uint32_t rgb = default_palette[palette_map[c]];
|
||||
int r = ((rgb >> 16) & 0xFF) * 100 / 255;
|
||||
int g = ((rgb >> 8) & 0xFF) * 100 / 255;
|
||||
int b = (rgb & 0xFF) * 100 / 255;
|
||||
@@ -462,6 +512,165 @@ void gfx_flush(void)
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* VIEW / WINDOW / PALETTE
|
||||
* ================================================================ */
|
||||
|
||||
void gfx_view(bool screen_flag, int x1, int y1, int x2, int y2,
|
||||
int fill, bool has_fill, int border, bool has_border)
|
||||
{
|
||||
if (!framebuf) return;
|
||||
|
||||
/* Normalize */
|
||||
if (x1 > x2) { int t = x1; x1 = x2; x2 = t; }
|
||||
if (y1 > y2) { int t = y1; y1 = y2; y2 = t; }
|
||||
|
||||
/* Clamp to screen */
|
||||
if (x1 < 0) x1 = 0;
|
||||
if (y1 < 0) y1 = 0;
|
||||
if (x2 >= fb_width) x2 = fb_width - 1;
|
||||
if (y2 >= fb_height) y2 = fb_height - 1;
|
||||
|
||||
vp_active = true;
|
||||
vp_screen = screen_flag;
|
||||
vp_x1 = x1; vp_y1 = y1;
|
||||
vp_x2 = x2; vp_y2 = y2;
|
||||
|
||||
/* Draw border (outside viewport, on full framebuffer) */
|
||||
if (has_border) {
|
||||
bool save = vp_active;
|
||||
vp_active = false;
|
||||
if (x1 > 0) draw_line(x1 - 1, y1 > 0 ? y1 - 1 : y1, x1 - 1, y2 < fb_height - 1 ? y2 + 1 : y2, border);
|
||||
if (x2 < fb_width - 1) draw_line(x2 + 1, y1 > 0 ? y1 - 1 : y1, x2 + 1, y2 < fb_height - 1 ? y2 + 1 : y2, border);
|
||||
if (y1 > 0) draw_line(x1 > 0 ? x1 - 1 : x1, y1 - 1, x2 < fb_width - 1 ? x2 + 1 : x2, y1 - 1, border);
|
||||
if (y2 < fb_height - 1) draw_line(x1 > 0 ? x1 - 1 : x1, y2 + 1, x2 < fb_width - 1 ? x2 + 1 : x2, y2 + 1, border);
|
||||
vp_active = save;
|
||||
}
|
||||
|
||||
/* Fill viewport */
|
||||
if (has_fill) {
|
||||
for (int y = vp_y1; y <= vp_y2; y++)
|
||||
for (int x = vp_x1; x <= vp_x2; x++)
|
||||
framebuf[y * fb_width + x] = fill;
|
||||
}
|
||||
|
||||
/* Reset last position to viewport origin */
|
||||
last_x = vp_x1;
|
||||
last_y = vp_y1;
|
||||
|
||||
/* Reset WINDOW when VIEW changes */
|
||||
win_active = false;
|
||||
}
|
||||
|
||||
void gfx_view_reset(void)
|
||||
{
|
||||
vp_active = false;
|
||||
if (framebuf) {
|
||||
vp_x1 = 0; vp_y1 = 0;
|
||||
vp_x2 = fb_width - 1; vp_y2 = fb_height - 1;
|
||||
}
|
||||
win_active = false;
|
||||
last_x = 0;
|
||||
last_y = 0;
|
||||
}
|
||||
|
||||
void gfx_window(bool screen_flag, double x1, double y1, double x2, double y2)
|
||||
{
|
||||
win_active = true;
|
||||
win_screen = screen_flag;
|
||||
win_x1 = x1; win_y1 = y1;
|
||||
win_x2 = x2; win_y2 = y2;
|
||||
}
|
||||
|
||||
void gfx_window_reset(void)
|
||||
{
|
||||
win_active = false;
|
||||
}
|
||||
|
||||
bool gfx_has_window(void)
|
||||
{
|
||||
return win_active;
|
||||
}
|
||||
|
||||
int gfx_map_x(double x)
|
||||
{
|
||||
int left = vp_active ? vp_x1 : 0;
|
||||
int right = vp_active ? vp_x2 : (fb_width - 1);
|
||||
|
||||
if (win_active) {
|
||||
if (win_x2 == win_x1) return left;
|
||||
return left + (int)((x - win_x1) / (win_x2 - win_x1) * (right - left) + 0.5);
|
||||
}
|
||||
if (vp_active && !vp_screen)
|
||||
return left + (int)x;
|
||||
return (int)x;
|
||||
}
|
||||
|
||||
int gfx_map_y(double y)
|
||||
{
|
||||
int top = vp_active ? vp_y1 : 0;
|
||||
int bottom = vp_active ? vp_y2 : (fb_height - 1);
|
||||
|
||||
if (win_active) {
|
||||
if (win_y2 == win_y1) return top;
|
||||
if (win_screen) {
|
||||
/* WINDOW SCREEN: Y increases downward */
|
||||
return top + (int)((y - win_y1) / (win_y2 - win_y1) * (bottom - top) + 0.5);
|
||||
} else {
|
||||
/* WINDOW (Cartesian): Y increases upward */
|
||||
return bottom - (int)((y - win_y1) / (win_y2 - win_y1) * (bottom - top) + 0.5);
|
||||
}
|
||||
}
|
||||
if (vp_active && !vp_screen)
|
||||
return top + (int)y;
|
||||
return (int)y;
|
||||
}
|
||||
|
||||
double gfx_pmap(double coord, int func)
|
||||
{
|
||||
int left = vp_active ? vp_x1 : 0;
|
||||
int right = vp_active ? vp_x2 : (fb_width - 1);
|
||||
int top = vp_active ? vp_y1 : 0;
|
||||
int bottom = vp_active ? vp_y2 : (fb_height - 1);
|
||||
|
||||
if (!win_active) return coord;
|
||||
|
||||
switch (func) {
|
||||
case 0: /* logical X → physical X */
|
||||
if (win_x2 == win_x1) return left;
|
||||
return left + (coord - win_x1) / (win_x2 - win_x1) * (right - left);
|
||||
case 1: /* logical Y → physical Y */
|
||||
if (win_y2 == win_y1) return top;
|
||||
if (win_screen)
|
||||
return top + (coord - win_y1) / (win_y2 - win_y1) * (bottom - top);
|
||||
else
|
||||
return bottom - (coord - win_y1) / (win_y2 - win_y1) * (bottom - top);
|
||||
case 2: /* physical X → logical X */
|
||||
if (right == left) return win_x1;
|
||||
return win_x1 + (coord - left) / (double)(right - left) * (win_x2 - win_x1);
|
||||
case 3: /* physical Y → logical Y */
|
||||
if (bottom == top) return win_y1;
|
||||
if (win_screen)
|
||||
return win_y1 + (coord - top) / (double)(bottom - top) * (win_y2 - win_y1);
|
||||
else
|
||||
return win_y1 + (bottom - coord) / (double)(bottom - top) * (win_y2 - win_y1);
|
||||
default:
|
||||
return coord;
|
||||
}
|
||||
}
|
||||
|
||||
void gfx_palette_set(int attr, int color)
|
||||
{
|
||||
if (attr < 0 || attr > 15 || color < 0 || color > 15) return;
|
||||
palette_map[attr] = color;
|
||||
palette[attr] = default_palette[color];
|
||||
}
|
||||
|
||||
void gfx_palette_reset(void)
|
||||
{
|
||||
reset_palette();
|
||||
}
|
||||
|
||||
/*
|
||||
* CGA framebuffer PEEK/POKE.
|
||||
*
|
||||
|
||||
150
src/interp.c
150
src/interp.c
@@ -723,22 +723,41 @@ void gw_stmt_chain(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* Evaluate a graphics coordinate, mapping through WINDOW/VIEW */
|
||||
static int eval_gfx_x(void)
|
||||
{
|
||||
if (gfx_has_window()) {
|
||||
gw_value_t v = gw_eval_num();
|
||||
return gfx_map_x(gw_to_dbl(&v));
|
||||
}
|
||||
return gfx_map_x((double)gw_eval_int());
|
||||
}
|
||||
|
||||
static int eval_gfx_y(void)
|
||||
{
|
||||
if (gfx_has_window()) {
|
||||
gw_value_t v = gw_eval_num();
|
||||
return gfx_map_y(gw_to_dbl(&v));
|
||||
}
|
||||
return gfx_map_y((double)gw_eval_int());
|
||||
}
|
||||
|
||||
/* Graphics GET (x1,y1)-(x2,y2), array */
|
||||
static void stmt_gfx_get(void)
|
||||
{
|
||||
gw_expect('(');
|
||||
int x1 = gw_eval_int();
|
||||
int x1 = eval_gfx_x();
|
||||
gw_skip_spaces();
|
||||
gw_expect(',');
|
||||
int y1 = gw_eval_int();
|
||||
int y1 = eval_gfx_y();
|
||||
gw_expect_rparen();
|
||||
gw_skip_spaces();
|
||||
gw_expect(TOK_MINUS);
|
||||
gw_expect('(');
|
||||
int x2 = gw_eval_int();
|
||||
int x2 = eval_gfx_x();
|
||||
gw_skip_spaces();
|
||||
gw_expect(',');
|
||||
int y2 = gw_eval_int();
|
||||
int y2 = eval_gfx_y();
|
||||
gw_expect_rparen();
|
||||
gw_skip_spaces();
|
||||
gw_expect(',');
|
||||
@@ -781,10 +800,10 @@ static void stmt_gfx_get(void)
|
||||
static void stmt_gfx_put(void)
|
||||
{
|
||||
gw_expect('(');
|
||||
int x = gw_eval_int();
|
||||
int x = eval_gfx_x();
|
||||
gw_skip_spaces();
|
||||
gw_expect(',');
|
||||
int y = gw_eval_int();
|
||||
int y = eval_gfx_y();
|
||||
gw_expect_rparen();
|
||||
gw_skip_spaces();
|
||||
gw_expect(',');
|
||||
@@ -1002,9 +1021,9 @@ void gw_exec_stmt(void)
|
||||
gw_chrget();
|
||||
gw_skip_spaces();
|
||||
gw_expect('(');
|
||||
int cx = gw_eval_int();
|
||||
int cx = eval_gfx_x();
|
||||
gw_expect(',');
|
||||
int cy = gw_eval_int();
|
||||
int cy = eval_gfx_y();
|
||||
gw_expect_rparen();
|
||||
gw_expect(',');
|
||||
int radius = gw_eval_int();
|
||||
@@ -1059,9 +1078,9 @@ void gw_exec_stmt(void)
|
||||
gw_chrget();
|
||||
gw_skip_spaces();
|
||||
gw_expect('(');
|
||||
int px = gw_eval_int();
|
||||
int px = eval_gfx_x();
|
||||
gw_expect(',');
|
||||
int py = gw_eval_int();
|
||||
int py = eval_gfx_y();
|
||||
gw_expect_rparen();
|
||||
int fill_c = gfx_get_color(), border_c = 0;
|
||||
gw_skip_spaces();
|
||||
@@ -1228,12 +1247,101 @@ void gw_exec_stmt(void)
|
||||
}
|
||||
gw_error(ERR_SN);
|
||||
}
|
||||
/* Stubs: VIEW, WINDOW, PALETTE */
|
||||
if (xstmt == XSTMT_VIEW ||
|
||||
xstmt == XSTMT_WINDOW || xstmt == XSTMT_PALETTE) {
|
||||
/* VIEW [[SCREEN] (x1,y1)-(x2,y2) [,[fill][,border]]] */
|
||||
if (xstmt == XSTMT_VIEW) {
|
||||
gw_chrget();
|
||||
while (gw_chrgot() && gw_chrgot() != ':' && gw_chrgot() != TOK_ELSE)
|
||||
gw.text_ptr++;
|
||||
gw_skip_spaces();
|
||||
if (gw_chrgot() == 0 || gw_chrgot() == ':' || gw_chrgot() == TOK_ELSE) {
|
||||
gfx_view_reset();
|
||||
return;
|
||||
}
|
||||
bool scr = false;
|
||||
if (gw_chrgot() == TOK_SCREEN) {
|
||||
scr = true;
|
||||
gw_chrget();
|
||||
gw_skip_spaces();
|
||||
}
|
||||
/* Check for PRINT after VIEW to handle VIEW PRINT (ignore) */
|
||||
if (gw_chrgot() == TOK_PRINT) {
|
||||
while (gw_chrgot() && gw_chrgot() != ':' && gw_chrgot() != TOK_ELSE)
|
||||
gw.text_ptr++;
|
||||
return;
|
||||
}
|
||||
gw_expect('(');
|
||||
int x1 = gw_eval_int();
|
||||
gw_expect(',');
|
||||
int y1 = gw_eval_int();
|
||||
gw_expect_rparen();
|
||||
gw_skip_spaces();
|
||||
gw_expect(TOK_MINUS);
|
||||
gw_expect('(');
|
||||
int x2 = gw_eval_int();
|
||||
gw_expect(',');
|
||||
int y2 = gw_eval_int();
|
||||
gw_expect_rparen();
|
||||
int fill = 0, border = 0;
|
||||
bool has_fill = false, has_border = false;
|
||||
gw_skip_spaces();
|
||||
if (gw_chrgot() == ',') {
|
||||
gw_chrget();
|
||||
gw_skip_spaces();
|
||||
if (gw_chrgot() != ',' && gw_chrgot() != 0 && gw_chrgot() != ':' &&
|
||||
gw_chrgot() != TOK_ELSE) {
|
||||
fill = gw_eval_int();
|
||||
has_fill = true;
|
||||
}
|
||||
gw_skip_spaces();
|
||||
if (gw_chrgot() == ',') {
|
||||
gw_chrget();
|
||||
border = gw_eval_int();
|
||||
has_border = true;
|
||||
}
|
||||
}
|
||||
gfx_view(scr, x1, y1, x2, y2, fill, has_fill, border, has_border);
|
||||
return;
|
||||
}
|
||||
/* WINDOW [[SCREEN] (x1,y1)-(x2,y2)] */
|
||||
if (xstmt == XSTMT_WINDOW) {
|
||||
gw_chrget();
|
||||
gw_skip_spaces();
|
||||
if (gw_chrgot() == 0 || gw_chrgot() == ':' || gw_chrgot() == TOK_ELSE) {
|
||||
gfx_window_reset();
|
||||
return;
|
||||
}
|
||||
bool scr = false;
|
||||
if (gw_chrgot() == TOK_SCREEN) {
|
||||
scr = true;
|
||||
gw_chrget();
|
||||
gw_skip_spaces();
|
||||
}
|
||||
gw_expect('(');
|
||||
gw_value_t vx1 = gw_eval_num();
|
||||
gw_expect(',');
|
||||
gw_value_t vy1 = gw_eval_num();
|
||||
gw_expect_rparen();
|
||||
gw_skip_spaces();
|
||||
gw_expect(TOK_MINUS);
|
||||
gw_expect('(');
|
||||
gw_value_t vx2 = gw_eval_num();
|
||||
gw_expect(',');
|
||||
gw_value_t vy2 = gw_eval_num();
|
||||
gw_expect_rparen();
|
||||
gfx_window(scr, gw_to_dbl(&vx1), gw_to_dbl(&vy1),
|
||||
gw_to_dbl(&vx2), gw_to_dbl(&vy2));
|
||||
return;
|
||||
}
|
||||
/* PALETTE [attribute, color] */
|
||||
if (xstmt == XSTMT_PALETTE) {
|
||||
gw_chrget();
|
||||
gw_skip_spaces();
|
||||
if (gw_chrgot() == 0 || gw_chrgot() == ':' || gw_chrgot() == TOK_ELSE) {
|
||||
gfx_palette_reset();
|
||||
return;
|
||||
}
|
||||
int attr = gw_eval_int();
|
||||
gw_expect(',');
|
||||
int color = gw_eval_int();
|
||||
gfx_palette_set(attr, color);
|
||||
return;
|
||||
}
|
||||
gw.text_ptr = save;
|
||||
@@ -2188,9 +2296,9 @@ void gw_exec_stmt(void)
|
||||
gw_skip_spaces();
|
||||
if (gw_chrgot() == '(') {
|
||||
gw_chrget();
|
||||
x1 = gw_eval_int();
|
||||
x1 = eval_gfx_x();
|
||||
gw_expect(',');
|
||||
y1 = gw_eval_int();
|
||||
y1 = eval_gfx_y();
|
||||
gw_expect_rparen();
|
||||
} else {
|
||||
gfx_get_last(&x1, &y1);
|
||||
@@ -2198,9 +2306,9 @@ void gw_exec_stmt(void)
|
||||
gw_skip_spaces();
|
||||
gw_expect(TOK_MINUS);
|
||||
gw_expect('(');
|
||||
x2 = gw_eval_int();
|
||||
x2 = eval_gfx_x();
|
||||
gw_expect(',');
|
||||
y2 = gw_eval_int();
|
||||
y2 = eval_gfx_y();
|
||||
gw_expect_rparen();
|
||||
int color = gfx_get_color();
|
||||
int style = GFX_LINE;
|
||||
@@ -2484,9 +2592,9 @@ void gw_exec_stmt(void)
|
||||
gw_chrget();
|
||||
gw_skip_spaces();
|
||||
gw_expect('(');
|
||||
int px = gw_eval_int();
|
||||
int px = eval_gfx_x();
|
||||
gw_expect(',');
|
||||
int py = gw_eval_int();
|
||||
int py = eval_gfx_y();
|
||||
gw_expect_rparen();
|
||||
int color = is_preset ? 0 : gfx_get_color();
|
||||
gw_skip_spaces();
|
||||
|
||||
@@ -256,7 +256,8 @@ float gw_mbf_to_ieee_single(mbf_single_t mbf)
|
||||
int exp = (int)mbf.exponent - 128 - 1 + 127; /* MBF bias 128, IEEE bias 127 */
|
||||
if (exp < 0 || exp > 254) return 0.0f;
|
||||
|
||||
ieee = ((uint32_t)sign << 24) | ((uint32_t)exp << 23) | ((mantissa >> 1) & 0x7FFFFF);
|
||||
/* Hidden 1 at bit 23 of mantissa, IEEE stores bits 0-22 (fraction only) */
|
||||
ieee = ((uint32_t)sign << 24) | ((uint32_t)exp << 23) | (mantissa & 0x7FFFFF);
|
||||
float result;
|
||||
memcpy(&result, &ieee, 4);
|
||||
return result;
|
||||
@@ -275,7 +276,9 @@ double gw_mbf_to_ieee_double(mbf_double_t mbf)
|
||||
int exp = (int)mbf.exponent - 128 - 1 + 1023;
|
||||
if (exp < 0 || exp > 2046) return 0.0;
|
||||
|
||||
ieee = ((uint64_t)sign << 56) | ((uint64_t)exp << 52) | ((mantissa >> 4) & 0x000FFFFFFFFFFFFF);
|
||||
/* Hidden 1 at bit 55 of mantissa, IEEE stores bits 0-51 (fraction only).
|
||||
* MBF has 55 fraction bits (3 more than IEEE), shift right 3 to align. */
|
||||
ieee = ((uint64_t)sign << 56) | ((uint64_t)exp << 52) | ((mantissa >> 3) & 0x000FFFFFFFFFFFFF);
|
||||
double result;
|
||||
memcpy(&result, &ieee, 8);
|
||||
return result;
|
||||
@@ -297,7 +300,7 @@ mbf_single_t gw_ieee_to_mbf_single(float f)
|
||||
if (exp > 255) exp = 255;
|
||||
|
||||
mbf.exponent = exp;
|
||||
mantissa <<= 1;
|
||||
/* Hidden 1 at bit 23, sign replaces it in byte 2 bit 7 */
|
||||
mbf.mantissa[0] = mantissa & 0xFF;
|
||||
mbf.mantissa[1] = (mantissa >> 8) & 0xFF;
|
||||
mbf.mantissa[2] = ((mantissa >> 16) & 0x7F) | (sign << 7);
|
||||
@@ -321,7 +324,9 @@ mbf_double_t gw_ieee_to_mbf_double(double d)
|
||||
if (exp > 255) exp = 255;
|
||||
|
||||
mbf.exponent = exp;
|
||||
mantissa <<= 4;
|
||||
/* Hidden 1 at bit 52, MBF stores 55 fraction bits + sign.
|
||||
* Shift left 3 to place hidden 1 at bit 55 (byte 6 bit 7 = sign). */
|
||||
mantissa <<= 3;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (i == 6)
|
||||
mbf.mantissa[i] = ((mantissa >> (i * 8)) & 0x7F) | (sign << 7);
|
||||
|
||||
15
tests/expected/mbf_format.expected
Normal file
15
tests/expected/mbf_format.expected
Normal file
@@ -0,0 +1,15 @@
|
||||
0
|
||||
1
|
||||
-1
|
||||
3.14
|
||||
100.5
|
||||
0
|
||||
1
|
||||
-1
|
||||
3.14
|
||||
0
|
||||
32767
|
||||
-32768
|
||||
-1
|
||||
0 0 0 129
|
||||
DONE
|
||||
7
tests/expected/view_window.expected
Normal file
7
tests/expected/view_window.expected
Normal file
@@ -0,0 +1,7 @@
|
||||
PMAP(0,0)= 0
|
||||
PMAP(0,1)= 199
|
||||
PMAP(100,0)= 639
|
||||
PMAP(100,1)= 0
|
||||
PMAP(0,2)= 0
|
||||
PMAP(199,3)= 0
|
||||
VIEW/WINDOW/PALETTE OK
|
||||
22
tests/programs/mbf_format.bas
Normal file
22
tests/programs/mbf_format.bas
Normal file
@@ -0,0 +1,22 @@
|
||||
10 REM MBF format conversion test
|
||||
20 REM Test MKS$/CVS roundtrip
|
||||
30 PRINT CVS(MKS$(0))
|
||||
40 PRINT CVS(MKS$(1))
|
||||
50 PRINT CVS(MKS$(-1))
|
||||
60 PRINT CVS(MKS$(3.14))
|
||||
70 PRINT CVS(MKS$(100.5))
|
||||
80 REM Test MKD$/CVD roundtrip
|
||||
90 PRINT CVD(MKD$(0#))
|
||||
100 PRINT CVD(MKD$(1#))
|
||||
110 PRINT CVD(MKD$(-1#))
|
||||
120 PRINT CVD(MKD$(3.14#))
|
||||
130 REM Test MKI$/CVI roundtrip
|
||||
140 PRINT CVI(MKI$(0))
|
||||
150 PRINT CVI(MKI$(32767))
|
||||
160 PRINT CVI(MKI$(-32768))
|
||||
170 PRINT CVI(MKI$(-1))
|
||||
180 REM Test MBF byte encoding
|
||||
190 REM MKS$(1) in MBF should be: 00 00 00 81 (exponent=129, mantissa=0.5)
|
||||
200 A$ = MKS$(1)
|
||||
210 PRINT ASC(MID$(A$,1,1)); ASC(MID$(A$,2,1)); ASC(MID$(A$,3,1)); ASC(MID$(A$,4,1))
|
||||
220 PRINT "DONE"
|
||||
21
tests/programs/view_window.bas
Normal file
21
tests/programs/view_window.bas
Normal file
@@ -0,0 +1,21 @@
|
||||
10 REM VIEW / WINDOW / PALETTE test
|
||||
20 SCREEN 2
|
||||
30 REM Test WINDOW coordinate mapping
|
||||
40 WINDOW (0,0)-(100,100)
|
||||
50 REM PMAP: logical to physical
|
||||
60 PRINT "PMAP(0,0)="; PMAP(0,0)
|
||||
70 PRINT "PMAP(0,1)="; PMAP(0,1)
|
||||
80 PRINT "PMAP(100,0)="; PMAP(100,0)
|
||||
90 PRINT "PMAP(100,1)="; PMAP(100,1)
|
||||
100 REM PMAP: physical to logical
|
||||
110 PRINT "PMAP(0,2)="; PMAP(0,2)
|
||||
120 PRINT "PMAP(199,3)="; PMAP(199,3)
|
||||
130 WINDOW
|
||||
140 REM Test VIEW
|
||||
150 VIEW (10,10)-(100,50)
|
||||
160 REM After VIEW, drawing clips to viewport
|
||||
170 VIEW
|
||||
180 REM Test PALETTE (just reset, no visual check)
|
||||
190 PALETTE
|
||||
200 SCREEN 0
|
||||
210 PRINT "VIEW/WINDOW/PALETTE OK"
|
||||
Reference in New Issue
Block a user