From c34c43901468a40316b133b016babb6587793d0d Mon Sep 17 00:00:00 2001 From: Alvaro Date: Mon, 21 May 2012 20:18:55 +0100 Subject: [PATCH] Add JSW --- jsw/jsw.html | 212 ++++++ jsw/jsw.ino | 1576 ++++++++++++++++++++++++++++++++++++++++++++ jsw/smallfs/JSW | Bin 0 -> 65536 bytes jsw/smallfs/ZPUINO | Bin 0 -> 6912 bytes jsw/style.css | 104 +++ 5 files changed, 1892 insertions(+) create mode 100644 jsw/jsw.html create mode 100644 jsw/jsw.ino create mode 100644 jsw/smallfs/JSW create mode 100644 jsw/smallfs/ZPUINO create mode 100644 jsw/style.css diff --git a/jsw/jsw.html b/jsw/jsw.html new file mode 100644 index 0000000..1a0d624 --- /dev/null +++ b/jsw/jsw.html @@ -0,0 +1,212 @@ + + + Jet Set Willy 48K information + + + + +
+ +This document presents some technical information about Jet Set Willt 48 original implementation +for ZX Spectrum 48K. It was written as a result of a implementation of the game engine to ZPUino +SoC with a compatible VGA interface.

+ +

ZX Spectrum 48K

+The ZX Spectrum 48K was a computer designed by Sinclair Research based on a Z80 CPU running at 3.5MHz, and a +special purpose chip built using a Ferranti ULA (so called the ZX Spectrum ULA). The ULA was responsable for +the video graphics and for the (limited) audio support.

+The ZX 80 memory map is roughly as follows:

+ + + + + + +
Start End Size Description
0x0000 0x3FFF 16384 bytes (16KB) ZX Spectrum ROM
0x4000 0x3FFF 6144 bytes Main screen area
0x5800 0x5CFF 768 bytes Screen attributes area
0x5B00 0xFFFF 42240 bytes Program/Data area
+ +

The screen

+ +The ZX Spectrum screen has an unusual memory layout. It's composed of a 256x192 pixel display with a 32x24 attribute block area. +Each attribute block entry sets the color for a 8x8 bit block on the screen.

+ +

JSW game

+The game consists of several rooms, filled with enemies and objects. +The objective of the game is to successfully collect all items in the game so Maria will allow you to go to bed.

+ +

The rooms

+The room is displayed on the game area, which is 32x16 blocks (8x8). Lives, time and other information are displayed below this area. +Each room consists of several actors and the room design itself.

+

The room design

+All rooms are composed of 4 types of blocks: + +The game engine uses the color (attribute) to distinguish from all block types, so each block type +should use it's own distinct color (attribute).

+Each of these types include a bitmap (8x8) per room, so each of this block type is drawn using the same color +and same bitmap. See below for the room binary description and where this data comes from. + +

The guardians

+The guardians are the little fellows that protect the objects from being collected. The guardians can be: + + +

Other entities

+Besides all the entities above, each room can also have: + + +
+ +
+Some elements of the game:

+1 - Arrow, 2 - Guardian, 3 - Nasty block, 4 - Slope +

+
+ +
+ +
+Some other elements of the game:

+5 - Wall block, 6 - Floor block, 7 - Conveyor, 8 - Object +

+
+ +

Collision detection

+Most collision detection is done using the attribute buffer, except for guardians (normal and arrows), where pixel-based collision +detection is used. + + +

Overall game memory layout

+The memory layout of the game (from 0x5B00, where screen attributes end) is as follows: + + + + + + + + + + + + + + + + + + + + +
Start End Size Description
0x5C00 0x5DFF 512 bytes 2nd attribute buffer (32x16)
0x5E00 0x5EFF 512 bytes 1nd attribute buffer (32x16)
0x6000 0x6FFF 4096 bytes 2nd screen buffer (32x16x8)
0x7000 0x7FFF 4096 bytes 1st screen buffer (32x16x8)
0x8000 0x80FF 256 bytes Current room definition
0x8100 0x8141 64+2 bytes Current room guardians
0x8142 0x81FF 190 bytes Not used
0x8200 0x82FF 256 bytes Pixel-buffer lookup table
0x8300 0x83FF 256 bytes Rope structure table
0x8400 0x96FF 4863 bytes Program code
0x9700 0x9FFF 2306 bytes Not used
0xA000 0xA3FE 1023 bytes Guardian table
0xA3FF 0xAAFF 1 + 1792 bytes Objects table (1st byte depicts number of objects)
0xAB00 0xBFFF 5376 bytes Sprites
0xC000 0xFFFF 16384 bytes Room definitions (256 bytes per room)
+ +

The room definition

+Each room is described by a 256-byte block. Whenever a new room is loaded, it's definition is copied from the room definition area (0xC000) to +the current room area (0x8000).

+The room structure is defined like this: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Start End Size Description
0 127 128 bytes Main room blocks, packed into 2 bit each
128 159 32 bytes Room name
160 160 1 byte Background attribute
161 168 8 bytes Background 8x8 pixel data
169 169 1 byte Floor attribute
170 177 8 bytes Floor 8x8 pixel data
178 178 1 byte Wall attribute
179 186 8 bytes Wall 8x8 pixel data
187 187 1 byte Nasty attribute
188 195 8 bytes Nasty 8x8 pixel data
196 196 1 byte Slope attribute
197 204 8 bytes Slope 8x8 pixel data
205 205 1 byte Conveyor attribute
206 213 8 bytes Conveyor 8x8 pixel data
214 214 1 byte Conveyor direction
215 216 2 bytes Conveyor start position a)
217 217 1 byte Conveyor length
218 218 1 byte Slope direction
219 220 2 bytes Slope start position a)
221 221 1 byte Slope length
222 224 3 bytes Screen (border) Background ?
225 232 8 bytes Objects on this map (up to 8)
233 233 1 byte Map ID if we exit left
234 234 1 byte Map ID if we exit right
235 235 1 byte Map ID if we exit up
236 236 1 byte Map ID if we exit down
237 238 2 bytes Willy start position on map a)
239 255 16 bytes Guardian instances (2 bytes each)
+ +Let's look at more detail into each field. + +

Main room blocks

+This data defines the whole 32x16 room area, in terms of 4 basic block types: background, floor, wall and nasty. +The encoding is done in MSB->LSB order, 2 bits at a time. So a binary value of AABBCCDDb depicts 4 consequent blocks, +where AA is the first block, BB the second one, and so on.

+These two bits, if multiplied by 9, give the offset of the block type in relation to the "Background attribute" in the room definition. + + + + + + + +
Bits Block type
00 Background
01 Floor
10 Wall
11 Nasty
+Since the game area is 32x16 blocks, and each block takes 2 bits, the room definition is 32x16x2 bits (1024 bits), or 128 bytes wide. + +

Block definitions

+The block definitions include Background, Floor, Wall and Nasty. These are used by the 128-byte room definition. In addition there +are two other block definitions, Slope and Conveyor. + +Each of those block definitions is 9-bytes wide. The 1st byte depicts the attribute (which is written to the attribute buffer and used +to test for collisions. The other 8 bytes are a 8x8 bitmap of the block itself. + +

Slopes

+Each map can have a slope. A slope is defined by its attribute and bitmap (like basic blocks), plus three other fields: direction, start +position and lenght. If length is zero, then no slope exists on this map.

+If lenght is not zero, then a slope is rendered to screen, using the direction (they are drawn left to right, so direction means either +a climbing slope or a falling slope), it's start position (which is an absolute value, and maps into the attribute buffer), and it's lenght +in 8x8 blocks in the horizontal direction. + +

Conveyor

+Each map can have a conveyor. A conveyor forces Willy into one direction (or to stop moving) when he's standing on it. +The Conveyor is similar to the Slope, except direction means the real conveyor direction (whether it forces Willy to go left +or right). They are always drawn horizontally. + +

Border

+Not yet sure. +

Object

+This is a 8-bit object bitmap for objects on this room. +

Left, Right, Up and Down

+These four fields depict the room number where to switch to when we go though each direction. + +

Willy start position

+This depicts the starting Willy position on this map. It's an absolute value, maps into the attribute buffer. +

Guardians

+This is perhaps the most complex field of the room definition. + + +

Links

+ZX-Spectrum graphics modes +
+ + diff --git a/jsw/jsw.ino b/jsw/jsw.ino new file mode 100644 index 0000000..33ade7a --- /dev/null +++ b/jsw/jsw.ino @@ -0,0 +1,1576 @@ +/*********************************************************** + * + * JSW 48K implementation for ZPUino with VGA ZX interface + * + * (C) Alvaro Lopes 2012 + * + * This code released under Creative Commons BY-NC-SA + * + * None of the additional files (ROM's and other resources) are + * allowed for commercial use. + * + * Based on information found in http://mdfs.net/Software/JSW/ + * ROM extracted from http://www.worldofspectrum.org + * + * Original ZX Spectrum ROM used by permission: + * http://www.worldofspectrum.org/permits/amstrad-roms.txt + * + * " Amstrad have kindly given their permission for the + * redistribution of their copyrighted material but retain + * that copyright " + * + * + * Based on commented disassembly as seen below: + * + ***** + ***** Based on: Jet Set Willy JSW48 Game Engine Source + ***** ====================================== + ***** JSW Copyright (C) 1984 Matthew Smith & Software Projects + ***** + ***** Commentary Copyright (C) 1985, 2004 J.G.Harston + ***** See http://mdfs.net/Software/JSW/ + ***** The source is assembleable with ZMac + ***** See http://mdfs.net/Software/Z80/ZMac + ***** + ***** This was originally a commented disassembly, but it became easier to type + ***** up as a source file and create the disassembly from it. + ***** + ********************************************************** +*/ + +#include + +// Define this is you use the ZPUino simulator +//#define SIMULATION + +#define STATIC static + +#ifdef SIMULATION + +#define JUMP_INVERT +#define LEFT_INVERT +#define RIGHT_INVERT +#define JUMP_PIN 25 +#define LEFT_PIN 27 +#define RIGHT_PIN 24 + +#else + +// Hephaestus (megawing) + +#define JUMP_PIN 36 +#define JUMP_INVERT +#define LEFT_PIN 39 +#define LEFT_INVERT +#define RIGHT_PIN 38 +#define RIGHT_INVERT + + +/*#define JUMP_PIN WING_C_3 + #define JUMP_INVERT + #define LEFT_PIN WING_C_5 + #define LEFT_INVERT + #define RIGHT_PIN WING_C_1 + #define RIGHT_INVERT + */ +#endif + +/* Some debugging functions */ + +void printnibble(unsigned int c) +{ + c&=0xf; + if (c>9) + Serial.write(c+'a'-10); + else + Serial.write(c+'0'); +} + +void printhexbyte(unsigned int c) +{ + printnibble(c>>4); + printnibble(c); +} + +void printhex(unsigned int c) +{ + printhexbyte(c>>24); + printhexbyte(c>>16); + printhexbyte(c>>8); + printhexbyte(c); +} + + +// This converts a attrbuffer offset to a framebuffer offset + +#define ATTRBUFFER_OFFSET_TO_FRAMEBUFFER_OFFSET(x) \ + ( ( (x) &0x1F) /* X offset */ + (((x)<<3) & ~0xFF) ) + +// Our main framebuffer + +static unsigned char framebuffer[32*24*8]; +static unsigned char pallete[32*24]; + +static unsigned int GAMEDELAY=50; + + +struct ROOM_s { + unsigned char def[128]; // OK + unsigned char name[32]; // OK + unsigned char background[9]; // OK + unsigned char floor[9]; // OK + unsigned char wall[9]; // OK + unsigned char nasty[9]; // OK + unsigned char slope[9]; // OK + unsigned char conveyor[9]; // OK + unsigned char conv_dir; // OK + unsigned char conv_psn[2]; // OK + unsigned char conv_num; // OK + unsigned char slope_dir; // OK + unsigned char slope_psn[2]; // OK + unsigned char slope_num; // OK + unsigned char border[3]; // OK + unsigned char object[8]; + unsigned char left; + unsigned char right; + unsigned char up; + unsigned char down; + unsigned char willisp[3]; + unsigned char instances[16]; +} __attribute__((packed)); + +static struct ROOM_s ROOM; + +static unsigned char guardian[66] ={ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0xFB +}; + +static unsigned char ROPE[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Rope X offsets + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,1,2,2,1,1,2,1,1,2,2,3,2,3,2, + 3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, // Rope Y offsets + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 4,6,6,4,6,4,6,4,6,4,4,4,6,4,4,4, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + + +static unsigned char HERE; // Current room number + +// Bitmaps for triangle characters +// ------------------------------- +static const unsigned char TBITMAP[] ={ + 0xC0,0xF0,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0xC0,0xF0,0xFC,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFC,0xF0,0xC0,0x00, + 0xFC,0xF0,0xC0,0x00,0x00,0x00,0x00,0x00}; + +const char AIR[] = "AIR"; + +const char MESSAGE[] = + "+++++ Press ENTER to Start +++++" + " JET-SET WILLY by Matthew Smith " + "\177 1984 SOFTWARE PROJECTS Ltd " + ". . . . .Guide Willy to collect " + "all the items around the house " + "before Midnight so Maria will let " + "you get to your bed. . . . . . ." + "+++++ Press ENTER to Start +++++"; + +char ITEMS[] = "Items collected 000 Time 00:00 m"; +const char GAME[] = "Game"; +const char OVER[] = "Over"; +char COLLECTED[] = "000"; +char NOWTIME[] = " 7:00a"; +char STARTTIME[] = " 7:00a"; + +// ==================== +unsigned char TICKER=0; +unsigned char LIVES=0; +unsigned char FLASH=0; + +// Current Willy state +// =================== +struct willy_state { + unsigned int YPOSN; + unsigned char DIRECTION; //L85D0; + unsigned char FALLING; + unsigned char FRAME; + unsigned int POSITION; + unsigned char JUMPING; + unsigned char ONROPE; +}; + +struct willy_state wstate; +struct willy_state save_wstate; + +unsigned char REMAIN=0; +unsigned char STATUS=0; +unsigned char COUNTDOWN=0; +unsigned char MTICK=0; +unsigned char MFLAGS=0; +unsigned char TELEPORT; +unsigned char TEMP; +unsigned int willy_offset=0; + +int newroom=1; // Set to 1 to load a new room. + +static unsigned char sprite[32]; // Willy sprite, used across the code + +const unsigned char MOONLIGHT[] = { + 0x51,0x3C,0x33,0x51,0x3C,0x33,0x51,0x3C,0x33,0x51,0x3C,0x33, + 0x51,0x3C,0x33,0x51,0x3C,0x33,0x51,0x3C,0x33,0x51,0x3C,0x33, + 0x4C,0x3C,0x33,0x4C,0x3C,0x33,0x4C,0x39,0x2D,0x4C,0x39,0x2D, + 0x51,0x40,0x2D,0x51,0x3C,0x33,0x51,0x3C,0x36,0x5B,0x40,0x36, + 0x66,0x51,0x3C,0x51,0x3C,0x33,0x51,0x3C,0x33,0x28,0x3C,0x28, + 0x28,0x36,0x2D,0x51,0x36,0x2D,0x51,0x36,0x2D,0x28,0x36,0x28, + 0x28,0x3C,0x33,0x51,0x3C,0x33,0x26,0x3C,0x2D,0x4C,0x3C,0x2D, + 0x28,0x40,0x33,0x51,0x40,0x33,0x2D,0x40,0x36,0x20,0x40,0x36, + 0x3D,0x79,0x3D,0xFF }; // Terminated with &FF + +const unsigned char RICHMAN[] = { + 0x56,0x60,0x56,0x60,0x66,0x66,0x80,0x80,0x80,0x80,0x66,0x60, + 0x56,0x60,0x56,0x60,0x66,0x60,0x56,0x4C,0x48,0x4C,0x48,0x4C, + 0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x40,0x40,0x40,0x40, + 0x44,0x44,0x4C,0x4C,0x56,0x60,0x66,0x60,0x56,0x56,0x66,0x66, + 0x51,0x56,0x60,0x56,0x51,0x51,0x60,0x60,0x40,0x40,0x40,0x40, + 0x40,0x40,0x40,0x40 }; // 64 bytes long, just loops + + +#define VGABASE IO_SLOT(8) +#define COLUMNS 32 +#define ROWS 24 + +#define RWIDTH 32*8 + +//SmallFSFile charmap; +SmallFSFile jswrom; + + +unsigned char *btarget; + +static inline void blit8x8(unsigned char value, void*) +{ + *btarget=value; + btarget+=COLUMNS; +} + + +static inline void putChar(unsigned char value) +{ + jswrom.seek( (value*8) + 0x3C00,SEEK_SET); + jswrom.readCallback(8, &blit8x8, NULL); + btarget-=((8*COLUMNS)-1); +} + +static inline void putString(const char *p) +{ + while (*p) { + putChar(*p++); + } +} + +STATIC void drawpower() +{ + unsigned off = 21*COLUMNS + 20; // Line 21, column 20 + memset( &pallete[off], 0x7, 10); + PRMESSAGE( &framebuffer[ ATTRBUFFER_OFFSET_TO_FRAMEBUFFER_OFFSET(off) ], "Powered by", 10); + off+=COLUMNS; + memset( &pallete[off], 0x5, 10); + PRMESSAGE( &framebuffer[ ATTRBUFFER_OFFSET_TO_FRAMEBUFFER_OFFSET(off) ], " ZPUino32 ", 10); + +} + +STATIC void updatepower() +{ + + unsigned off = 22*COLUMNS + 20; // Line 21, column 20 + unsigned i; + for (i=10;i!=0;i--) { + pallete[off] = ( pallete[off] & 0xf8 ) | // Lose INK + (TICKER & 0x3) + 0x3; + off++; + } + +} + + +STATIC void PRMATRIX(const unsigned char *source, unsigned char *dest) +{ + // Print a 8x8 matrix on screen. This loop is fully unrolled. + *dest = *source++; dest+=COLUMNS; + *dest = *source++; dest+=COLUMNS; + *dest = *source++; dest+=COLUMNS; + *dest = *source++; dest+=COLUMNS; + *dest = *source++; dest+=COLUMNS; + *dest = *source++; dest+=COLUMNS; + *dest = *source++; dest+=COLUMNS; + *dest = *source; +} + + +/* Update conveyors on screen */ + +STATIC void UPDATECONV() +{ + if (ROOM.conv_num==0) + return; + + unsigned v = ROOM.conveyor[1]; + + if (ROOM.conv_dir==0) { + // Left + v<<=2; + v|=(v>>8); + ROOM.conveyor[1] = v; + } else { + v|=(v<<8); + v>>=2; + ROOM.conveyor[1] = v; + } + + v = ROOM.conveyor[3]; + + if (ROOM.conv_dir==0) { + + // opposite + v|=(v<<8); + v>>=2; + + ROOM.conveyor[3] = v; + } else { + v<<=2; + v|=(v>>8); + + ROOM.conveyor[3] = v; + } +} + +/* + Draw a sprite (32x32) on the screen. Eventually check for collisions + */ + +STATIC int drawsprite(const unsigned char *source, unsigned char*dest, int check_collision) +{ + int i; + unsigned sprite_byte; + for (i=16;i!=0;i--) { + sprite_byte = *source; + if (check_collision) { + if (sprite_byte & (*dest)) { + return 1; + } + sprite_byte |= *dest; + } + *dest = sprite_byte; + dest++; + source++; + sprite_byte=*source; + if (check_collision) { + if (sprite_byte & (*dest)) { + return 1; + } + } + *dest = sprite_byte; + dest--; + dest+=COLUMNS; + source++; + } + return 0; +} + +STATIC void loadscr(SmallFSFile &scr) +{ + // SCR are tricky + + unsigned i,j,z; + for (z=0;z<3;z++) { + unsigned char *fbptr= &framebuffer[COLUMNS*(z*8*8)]; + + for (j=0;j<8;j++) { + unsigned char *tbptr=fbptr; + for (i=0;i<8;i++) { + scr.read( tbptr, COLUMNS ); + tbptr+=COLUMNS*8; + } + fbptr+=COLUMNS; + } + } + // Seek to pallete (not nedded, I think) + scr.seek( sizeof(framebuffer), SEEK_SET); + scr.read( &pallete[0], sizeof(pallete)); + +} + +STATIC void opening() +{ + SmallFSFile scr; + scr = SmallFS.open("ZPUINO"); + if (scr.valid()) { + + loadscr(scr); + // Wait for jump key + + while (!( JUMP_INVERT digitalRead(JUMP_PIN))) { + delay(100); + } + } +} + + +void setup() +{ + Serial.begin(115200); + + REGISTER(VGABASE,0) = (unsigned)framebuffer; + REGISTER(VGABASE,1) = (unsigned)pallete; + Serial.println("JSW starting"); + SmallFS.begin(); + + jswrom = SmallFS.open("JSW"); + + if (!jswrom.valid()) { + while (1) { + } + } + + pinMode(JUMP_PIN,INPUT); + pinMode(RIGHT_PIN,INPUT); + pinMode(LEFT_PIN,INPUT); + + opening(); +} + +void generic_copy_from_flash(unsigned offset, unsigned char *target, unsigned size) +{ + jswrom.seek(offset, SEEK_SET); + jswrom.read( target, size); +} + +void loop() +{ + GAMESTART(); +} + +static inline void copy_attributes() +{ + generic_copy_from_flash(0x9800,pallete,sizeof(pallete)); +} + + +STATIC void PRMESSAGE(unsigned char *dest, const char *source, unsigned size) +{ + btarget = dest; + while(size--) { + putChar(*source++); + } +} + +void changeBG() +{ + // TODO: implement this + +} + +STATIC int DRAWLIVES() +{ + unsigned l = LIVES; + if (l==0) + return 0; + + // TODO - use MTICK + if ((TICKER&0x3)!=0) + return 1; + + unsigned spritenum = TICKER>>2; + spritenum *= 8; + spritenum &= 0x60; // Ensure 0, 1, 2 or 3 + + unsigned char *dest = &framebuffer[ 8*COLUMNS * 21 ]; // Line 21 + + generic_copy_from_flash(spritenum + 0x9D00, sprite, sizeof(sprite)); + + while (l--) { + drawsprite(sprite, dest, 0); + dest+=2; + } +} + +STATIC void ROOMBLOCK(unsigned v, unsigned char *dest) +{ + *dest = ROOM.background[ (v&0x3)*9 ]; +} + +STATIC void BUILDROOM() +{ + unsigned char *rb = &ROOM.def[0]; + unsigned char *attrptr = &pallete[0]; + unsigned roombyte; + int i; + + for (i=128;i!=0;i--) { + roombyte=*rb; + ROOMBLOCK(roombyte>>6, attrptr++); + ROOMBLOCK(roombyte>>4, attrptr++); + ROOMBLOCK(roombyte>>2, attrptr++); + ROOMBLOCK(roombyte>>0, attrptr++); + rb++; + } + + i=ROOM.conv_num; + + if (i!=0) { + unsigned start = (ROOM.conv_psn[0] +( ROOM.conv_psn[1]<<8)) - 0x5E00; + attrptr = &pallete[start]; + + while (i--) { + *attrptr++=ROOM.conveyor[0]; + } + } + + if (ROOM.slope_num==0) + return; + + attrptr = &pallete[ ((unsigned)ROOM.slope_psn[0]+ ((unsigned)ROOM.slope_psn[1]<<8)) - 0x5E00]; + + int delta; + if ( ROOM.slope_dir & 1 ) { + delta = -31; + } else { + delta = -33; + } + + for (i=ROOM.slope_num;i!=0;i--) { + *attrptr = ROOM.slope[0]; + attrptr+=delta; + } +} + +STATIC void PRMATRIX_if(unsigned val, const unsigned char *src, unsigned char *dest) +{ + if (val == *src) { + PRMATRIX(src+1,dest); + } +} + +STATIC void DRAWROOM() +{ + /* Iterate through all attributes, apply bitmap */ + unsigned int i,z; + unsigned char *attrptr = &pallete[0]; + unsigned char *fbptr = &framebuffer[0]; + + for (i=0; i<512; i++) { + unsigned int val = *attrptr; + unsigned char *src = &ROOM.background[0]; + // Unroll loop + PRMATRIX_if(val,src,fbptr); + PRMATRIX_if(val,src+9,fbptr); + PRMATRIX_if(val,src+18,fbptr); + PRMATRIX_if(val,src+27,fbptr); + PRMATRIX_if(val,src+36,fbptr); + PRMATRIX_if(val,src+45,fbptr); + fbptr++; + + // Move to next line + + if ((i%32)==31) { + fbptr+=(COLUMNS*7); + } + attrptr++; + } +} + +#define DUMPROOM( dname, vname ) \ + rdump( dname, (unsigned char*)&ROOM + offsetof(ROOM_s, vname), sizeof(ROOM.vname) ); + +STATIC void loadroom() +{ + unsigned i; + + generic_copy_from_flash( 0xC000 + ((unsigned)HERE*256), (unsigned char*)&ROOM, 256 ); + + unsigned char *gptr=&ROOM.instances[0]; + + unsigned char *dptr=guardian; + + for (i=0;i<8;i++) { + unsigned guardian_number = *gptr++; + + /*Serial.print("Read Guardian "); + printhexbyte(guardian_number); + Serial.println("");*/ + + guardian_number &= 0x7F; // Max. 127 guardians + guardian_number |= 0x1400; + guardian_number*=8; + + // Now, guardian_number is address IN ROM. Copy parts + + generic_copy_from_flash(guardian_number,&dptr[0],2); + /* + jswrom.seek(guardian_number,SEEK_SET); + jswrom.read( &dptr[0], 2);*/ + // + // Copy the rest + jswrom.read( &dptr[2], 6); + + // Set attr? + dptr[2] = *gptr++; + + dptr+=8; + } + + // Print room information + + memset( &framebuffer[ 16*COLUMNS*8 ], 0x0, sizeof(framebuffer)-(16*COLUMNS*8) ); + PRMESSAGE( &framebuffer[16*COLUMNS*8], (char*)ROOM.name, 32); + PRMESSAGE( &framebuffer[19*COLUMNS*8], (char*)ITEMS, 32); + // Update collected + PRMESSAGE( &framebuffer[19*COLUMNS*8 + 16], (char*)COLLECTED, 3); + drawpower(); + memcpy(&save_wstate, &wstate, sizeof(wstate)); // Save willy state +} + +STATIC void GAMESTART() +{ + MTICK=FLASH=TICKER=COUNTDOWN=STATUS = 0; + + wstate.FALLING=0; // Not falling. + + LIVES=7; + + HERE = 0x21;// The bathroom + + wstate.POSITION=0x5DB4; // Position in attr buffer2 + wstate.YPOSN = 0xD0; + + COLLECTED[0]='0'; + COLLECTED[1]='0'; + COLLECTED[2]='0'; + + //REMAIN=OBJECTS[0]; + + int i; + + // TODO : clear object flags + + MFLAGS=0; + + + unsigned deltas[9]; + + wstate.POSITION=0x5DB4; + wstate.YPOSN = 0xD0; + + copy_attributes(); + + do { + if (newroom) { + newroom=0; + loadroom(); + } + + + i=0; + TICKER++; + //Serial.println("Tick"); +#ifndef SIMULATION + + delay( GAMEDELAY ); + + // Wait for VGA retrace + while ((REGISTER(VGABASE,0)&1) == 0) {} + while ((REGISTER(VGABASE,0)&1) != 0) {} + while ((REGISTER(VGABASE,0)&1) == 0) {} + while ((REGISTER(VGABASE,0)&1) != 0) {} +#else + delay(GAMEDELAY); + REGISTER(VGABASE,3) = 1; // Disable screen +#endif + DRAWLIVES(); + bzero(framebuffer, 16*COLUMNS*8 ); // Only clear game-area + BUILDROOM(); + DRAWROOM(); + UPDATEGUARD(); + UPDATECONV(); + MOVEMENT(); + + if (wstate.YPOSN>=0xE1) { + go_up(); // NOTE: I think there's a bug here in some scenarios + continue; + } + + UPDATEWILLY(); + DRAWWILLY(); + DRAWGUARD(); + CHKOBJECTS(); + + updatepower(); +#ifdef SIMULATION + REGISTER(VGABASE,3) = 0; // Enable screen +#endif + } while (1); +} + +STATIC void DRAWWILLY() +{ + int A,E,DE,C; + unsigned i; + unsigned char *plb,*plbd; + + // willy_offset is used when we're on a stair. + + A = ((wstate.YPOSN+willy_offset)>>1)*32; + plb = &framebuffer[A]; // plb now points to start of line + + A = wstate.DIRECTION; + A &= 1; + + E = A<<7; + + A = wstate.FRAME; + A&=3; + A*=32; + E |= A; + + DE=E; + DE |= 0x9D00; + + generic_copy_from_flash(DE,sprite,sizeof(sprite)); + + plb += wstate.POSITION&0x1F; + + for (i=0;i<32;i+=2) /* 32 bytes */ + { + plbd = plb; + *plbd = *plbd | sprite[i]; + plbd++; + *plbd = *plbd | sprite[i+1]; + plb+=(COLUMNS); // Next line + } +} + +STATIC void DRAWROPE() +{ +} +STATIC void DRAWARROW(unsigned char *loc) +{ + unsigned comp; + if (loc[0] & 0x80) { + loc[4]++; + comp = 0xF4; + } else { + loc[4]--; + comp = 0x2C; + } + if (loc[4]!=comp) { + //? + } + // 82XX -pixel line + unsigned ypos = loc[2]; + ypos += loc[4]; + unsigned yagain=loc[2]; + yagain&=0x80; + yagain<<=1; + // OR 5C ? + yagain|=0x5C; // 5C00 / 5D00 + yagain|= (ypos & 0xff); + loc[5] = 0; + + // yagain shhould be willy attr + if ( ( pallete[yagain-0x5C00] & 0x7) == 0x07) { + // Willy + loc[5]--; // ? + } + + pallete[yagain-0x5C00] |= 0x7; + + yagain++; +} + +STATIC void DRAWGUARD() +{ + unsigned char *gptr = &guardian[0]; + + do { + unsigned i = *gptr; + /* Serial.print("Guardian: "); + printhexbyte(*gptr); + Serial.println("");*/ + if (i==0xff) + return; + + i&=7; + + if (i==0) { + // No guardian + //Serial.println("No guard"); + gptr+=8; + continue; + } + if (i==0x3) { /* Rope */ + //Serial.println("Rope"); + DRAWROPE(); + gptr+=8; + continue; + } + if (i==0x4) { /* Arrow */ + //Serial.println("Arrow"); + DRAWARROW(gptr); + gptr+=8; + continue; + } + /* Normal */ + /* Serial.print("Ypos "); + printhexbyte(gptr[3]); + Serial.println("");*/ + unsigned ypos = gptr[3]>>1; + unsigned xpos = (unsigned)gptr[2]; + + unsigned attr; + + attr = gptr[1]; // Get guardian attribute + attr&=0xf; // Keep b3-b0 + attr+=0x38; // Move BRIGHT up to bit 6 + attr&=0x47; // Keep INK and BRIGHT + + xpos &= 0x1F; // No overflow + + // Convert ypos. + + //Serial.print("FB at xpos ");Serial.print(xpos);Serial.print(" ypos ");Serial.println(ypos); + xpos+=ypos*32; + + unsigned yoffset = ((xpos/32)/8)*32; + + //Serial.print("Yoff "); Serial.println(yoffset); + + unsigned char *attrptr = &pallete[ (unsigned)(gptr[2]&0x1f) + yoffset];//( ((gptr[3]>>1)<<2)&(~0x1f) ) ]; + + + + + // Merge attribute + + attr = (*attrptr & 0x38 ) | attr; + + + // Two uppermost cells + *attrptr++ = attr; + *attrptr = attr; + attrptr+=31; // move to next line + + *attrptr++ =attr; + *attrptr =attr; + + // A guardian can use 2 or 3 lines. When (ypos/8)==0, only 2 lines are used. + + if ((gptr[3]) & 0xe ) { + //Serial.println("Guardian 3 lines"); + attrptr+=31; + *attrptr++=attr; // Fill the 3rd line + *attrptr=attr; + } + + /* Draw the guardian */ + + unsigned offset = (( gptr[1] & gptr[0]) | gptr[2]) & 0xE0; + offset|=((unsigned)gptr[5]<<8); + /* + Serial.print("Sprite at 0x"); + printhex(offset); + Serial.println(""); + */ + /* Seek to sprite */ + generic_copy_from_flash(offset,sprite,sizeof(sprite)); + + if ( drawsprite( sprite, &framebuffer[xpos], 1 ) ) { + //Serial.println("Kill: collision"); + kill_willy(); + return; + } + + gptr+=8; + + if (gptr>(&guardian[0]+sizeof(guardian))) + break; + + } while (1); +} + +STATIC void UPDATEGUARD() +{ + unsigned char *gptr = &guardian[0]; + + do { + unsigned i = *gptr; + /* + Serial.print("UpdateG: "); + printhexbyte(*gptr); + + Serial.println(""); + */ + if (i==0xff) + return; + + switch (i&0x03) { + case 0: + /* No need */ + break; + case 1: + /* Horizontal */ + /* + Serial.print("FRAME: "); + Serial.println( (i>>5) & 0x3 ); + */ + /* topmost bit depicts direction. 0->left, 1->right */ + if (i&0x80) { + i += 0x20; + i |= 0x80; /* ensure we're still moving right */ + // update position/frame + *gptr=i; + if ((i & 0x60) == 0x00) { // 1 01 000 00b + // We overflowed. + // Get X position + unsigned xpos = gptr[2]; + xpos &= 0x1F; + if (xpos==gptr[7]) { // Maximum X offset + gptr[0] = 0x61; // Clear direction, keep type (1). 0x61 == '0 11 000 01b' + } else { + // Increase X position + gptr[2] = gptr[2]+1; + } + } + } else { + // Left move + + i-=0x20; + i&=0x7f; // Reset high bit, if we underflowed + *gptr = i; + if (i>0x60) { // 0 11 000 01 b + // Serial.println("Underflow"); + unsigned xpos = gptr[2]; + xpos&=0x1f; + if (xpos==gptr[6]) // Minimum X + { + *gptr=0x81; // Clear direction, keep type '0 00 000 01' + } else { + gptr[2] = gptr[2] - 1; + } + } else { + + } + } + + break; + case 2: + /* + Serial.println("Vertical"); + + Serial.print("FRAME: "); + Serial.println( (i>>5) & 0x3 ); + */ + /* Vertical */ + i ^= 0x8; // '0 00 010 00 + *gptr=i; + + //if (i&0x18 != 0x00) { // 00001'1000b + i+=0x20; + *gptr=i; + //} + + i = gptr[3] + gptr[4]; + i&=0xff; // For later comparison + + gptr[3] = i; + + if (i >= gptr[7]) { + // JR NZ + // Serial.println("negate"); + gptr[4] = ~gptr[4] + 1; // NEG + + } else if (i<=gptr[6]) { + gptr[3] = gptr[6]; + gptr[4] = ~gptr[4] + 1; // NEG + } + break; + default: + break; + } + + gptr+=8; + + } while (1); +} + +STATIC int check_and_draw_willy_attribute(unsigned char *attrptr, int skip) +{ + if (*attrptr == ROOM.nasty[0]) { + return -1; + } + + if (skip) + return 0; + + if (*attrptr==ROOM.background[0]) { + *attrptr = ROOM.background[0] | 0x7; + } + + return 0; +} + +STATIC void UPDATEWILLY() +{ + unsigned pos = wstate.POSITION; + unsigned char *attrptr = &pallete[ pos - 0x5C00 ]; + + unsigned A = ROOM.slope_dir; + A&=1; + A+=0x40; + unsigned E=A; + unsigned B=0; + + pos += E; + + willy_offset = 0; + + // 0x40 or 0x41. See if standing on a slope + + if (ROOM.slope[0] == attrptr[E] ) { // Is a slope ? + + if (wstate.FALLING == 0) { + A = wstate.FRAME; + A&=0x3; + A<<=2; + + B = A; + A=ROOM.slope_dir; + A&=1; + A--; + A ^= 0xC; + A ^= B; + A &= 0xC; + B=A; + willy_offset = B; + + // This is used as offset for draw_willy + + + } + } + /* Check all 4 attributes */ + if (check_and_draw_willy_attribute(attrptr,0)<0) + return; + + attrptr++; + + if (check_and_draw_willy_attribute(attrptr,0)<0) + return; + + attrptr+=31; + + if (check_and_draw_willy_attribute(attrptr,0)<0) + return; + + attrptr++; + + if (check_and_draw_willy_attribute(attrptr,0)<0) + return; + + attrptr+=31; + + // Now, we might occupy 3 lines.... + + // Only check collisions, don't draw attribute unless we're overflowing YPOS + + // NOTE NOTE NOTE: This uses B avove, to interact with stairs: TODO + + if (check_and_draw_willy_attribute(attrptr, wstate.YPOSN&0xF!=0xF)<0) + return; + attrptr++; + if (check_and_draw_willy_attribute(attrptr, wstate.YPOSN&0xF!=0xF)<0) + return; +} + +STATIC unsigned char *wally_recompute_position() +{ + unsigned A = wstate.YPOSN; + unsigned current_line = (A>>1)/8; + unsigned current_col = wstate.POSITION & 0x1F; + wstate.POSITION = 0x5C00 + current_col + (current_line*32); + return &pallete[ wstate.POSITION - 0x5C00 ]; +} + +STATIC void kill_willy() +{ + //Serial.println("WILLY DIED"); + newroom = 1; + // Restore willy state + memcpy(&wstate, &save_wstate, sizeof(wstate)); +} + + +STATIC void L8EBC() { + wstate.YPOSN = ( wstate.YPOSN + 0x10 ) & 0xF0; + wally_recompute_position(); //L8E9C(); // Update position + + wstate.FALLING=2; + wstate.DIRECTION &= 0xFD; // clear bit 1 +} + +STATIC void move_willy()//L8FBC() +{ + if ( (wstate.DIRECTION & 0x2) == 0) + return; // don't update + /* + if ( ((wstate.ONROPE-1)&0x80) == 0) + return; */ + + //Serial.println("Updating willy"); + + if ((wstate.DIRECTION & 0x1) == 0) { + + if (wstate.FRAME==0x3) { + move_willy_right_block(); + return; + } + wstate.FRAME++; + wstate.FRAME &= 0x3; + return; + } + + if (wstate.FRAME==0) { + move_willy_left_block(); + return; + } + wstate.FRAME--; + wstate.FRAME &= 0x3; +} + +STATIC void MOVEMENT() +{ + unsigned l_FALLING = wstate.FALLING; + unsigned l_JUMPING = wstate.JUMPING; + unsigned l_YPOSN = wstate.YPOSN; + + if ( l_FALLING == 0x1) { + + // We are jumping + + l_YPOSN += (l_JUMPING & 0xfe) - 8; + + if (l_YPOSN >= 0xf0) { + // We underflow below 0 ? + // Go up + go_up(); + return; + } + + wstate.YPOSN = l_YPOSN; + + unsigned char *attrptr = wally_recompute_position(); + + if (attrptr[0] == ROOM.wall[0] || attrptr[1] == ROOM.wall[0] ) { + l_YPOSN += 0x10; + l_YPOSN &= 0xF0; + wstate.YPOSN = l_YPOSN; + wally_recompute_position(); + + wstate.FALLING=2; + wstate.DIRECTION &= 0xFD; + return; + } + + l_JUMPING = l_JUMPING + 1; + + if( l_JUMPING==0x12) { + wstate.FALLING=0x6; // We fell after jumping + wstate.JUMPING = l_JUMPING; + return; + } else if (l_JUMPING!=0x10) { + if( l_JUMPING!=0x0D) { // We are jumping downwards, and we have a block, check block + wstate.JUMPING=l_JUMPING; + return move_willy(); + } + } + wstate.JUMPING = l_JUMPING; + } + + if ( (l_YPOSN&0xE) == 0) { // If zero, we are are to move to lower block + + // Check if we can go down. + + unsigned HL = wstate.POSITION; + HL += 0x40; // Block under willy feet (2 lines) + if (HL & 0x200) { + // go down to room below (wrapped) + go_down(); + return; + } + + unsigned char *attrptr = &pallete[ HL - 0x5C00 ]; + + if (attrptr[0]==ROOM.nasty[0] || attrptr[1]==ROOM.nasty[0]) { + // Nasty block, die! + //Serial.println("Kill: nasty"); + kill_willy(); + return; + } + + // Now, see if its background + if (attrptr[0] != ROOM.background[0] || attrptr[1] != ROOM.background[0]) { + // Stop falling. + // Serial.println("Steady!"); + read_willy_input(); + return; + } + // falling.... + } + if (l_FALLING != 1) { + // We were not jumping, + + wstate.DIRECTION &= 0xFD; // clear bit 1 + + if (l_FALLING == 0) { + wstate.FALLING=2; + return; + } + + l_FALLING++; + + if (l_FALLING==0x10) { + l_FALLING=0x0C; + } + wstate.FALLING = l_FALLING; + wstate.YPOSN = l_YPOSN+8; + + // Trying to make a sense of this..... + wally_recompute_position(); + } else { + move_willy(); + } +} + +STATIC void read_willy_input() +{ + unsigned char *attrptr = &pallete[ wstate.POSITION - 0x5C00 ]; + static int countdown=-1; // TO allow jump+move in some situations + + if (wstate.FALLING >= 0x0C) { + //Serial.println("Kill: felt"); + kill_willy(); + return; + } + wstate.FALLING=0; + + unsigned k_jump = JUMP_INVERT digitalRead(JUMP_PIN); + unsigned k_left = LEFT_INVERT digitalRead(LEFT_PIN); + unsigned k_right = RIGHT_INVERT digitalRead(RIGHT_PIN); + + // Check block under willy feet + if (attrptr[64]==ROOM.conveyor[0] || attrptr[65]==ROOM.conveyor[0]) { + /* Standing on a conveyor */ + if (ROOM.conv_dir) { + k_left=1; + } else { + k_right=1; + } + } + + /* Now, for movement magic */ + + // Left and right always update direction lower bit + + // Unless both set - TODO + + unsigned old_direction = wstate.DIRECTION & 1; + + if (k_left) { + wstate.DIRECTION&=0xFE; + } + if (k_right) { + wstate.DIRECTION|=1; + } + + // Now, if we are jumping. + + if (k_jump) { + wstate.DIRECTION &= 0xFD; + + wstate.FALLING=1; + wstate.JUMPING=0; + wstate.YPOSN &= 0xF0; + + if (k_left || k_right) { + if (countdown<0) + countdown=2; + + if (old_direction == (wstate.DIRECTION&1)) { + // Same direction. + // if bot both set - then jump up + if (!(k_left & k_right)) { + wstate.DIRECTION |= 0x2; + } + if (countdown>0) + countdown--; + if (countdown==0) { + countdown=-1; + goto do_move; + } + } + } else { + countdown=-1; + } + + + } else { + countdown=-1; + do_move: + // Move if not both are set + if (k_left) { + if (!k_right) { + wstate.DIRECTION |= 0x2; + move_willy(); + } + } else { + if (k_right) { + wstate.DIRECTION |= 0x2; + move_willy(); + } + } + } +} + +STATIC void go_right() +{ + HERE=ROOM.right; + wstate.POSITION &= 0xffffffe0; // Reset our position + newroom=1; +} + + +STATIC void go_left() +{ + HERE=ROOM.left; + wstate.POSITION |= 0x1f; + wstate.POSITION &= 0xfffffffe; // Force into column 30 + newroom=1; +} + +STATIC void go_down() +{ + HERE=ROOM.down; + wstate.YPOSN=0; + if (wstate.FALLING>0x0B) { // TODO: review this + wstate.FALLING=2; + } + wstate.POSITION &= 0x1f; + wstate.POSITION += 0x5C00; + newroom=1; +} + +STATIC void go_up() +{ + HERE=ROOM.up; + wstate.POSITION&=0x1f; + wstate.POSITION|=0x1A0; + wstate.POSITION += 0x5C00; + //Serial.print("Going up, room "); + //printhexbyte(HERE); + //Serial.println(""); + wstate.YPOSN=0xD0; // 104, line 13 + wstate.FALLING=0; + newroom=1; +} + +STATIC int move_willy_right_block() +{ + // Move Willy rightwards + int voff = 0; + + unsigned char *attrptr = &pallete[ (wstate.POSITION-0x5C00) ]; + + unsigned yposoff=0; + if (wstate.FALLING==0) { + unsigned off = ROOM.slope_dir ? 34 : 64; + + if (attrptr[off] == ROOM.slope[0]) { + // On a slope, move up or down + + voff = ROOM.slope_dir ? -32 : 32; + yposoff = ROOM.slope_dir ? -16:16; + } + } + unsigned newpos = (wstate.POSITION - 0x5C00) + voff; + + if (((newpos+2) & 0x1f) == 0) { + + go_right(); + return 0; + } + + // TODO: check wall on 3rd line + + // Check wall + if ( pallete[newpos + 32 + 2] == ROOM.wall[0]) { // Is there a wall on right of willy foot ? + return 0; // No movement + } + if (pallete[newpos + 2]==ROOM.wall[0]) { // Check right of willy head + return 0; // + } + wstate.POSITION = newpos + 1 + 0x5C00; + wstate.FRAME = 0; + + wstate.YPOSN+=yposoff; +} + + +STATIC int move_willy_left_block() +{ + + int voff = 0; + + // 65 (slope=1) or 31 (slope=0):P + + unsigned yposoff=0; + + if (wstate.FALLING==0) { + unsigned char *attrptr = &pallete[wstate.POSITION-0x5C00]; + unsigned off = ROOM.slope_dir ? 65 : 31; + + if (attrptr[off] == ROOM.slope[0]) { + // On a slope, move up or down + voff = ROOM.slope_dir ? 32 : -32; + yposoff = ROOM.slope_dir ? 16:-16; + } + } + + unsigned newpos = (wstate.POSITION - 0x5C00) + voff; + + + if ((newpos & 0x1f)==0) { + + go_left(); + return 0; + } + + // TODO: check wall on 3rd line + + + if (pallete[newpos -1 ]==ROOM.wall[0]) { // Check left of willy head + return 0; // + } + if (pallete[newpos + 32 -1 ]==ROOM.wall[0]) { // Check left of willy head + return 0; // + } + + wstate.POSITION = newpos - 1 + 0x5C00; + wstate.FRAME = 0x3; + wstate.YPOSN += yposoff; +} + + +// A400 / A600 + +// we have two buffers, one with objects themselves, other with object flags and locations + +#define JSW_OBJCNT 0xad + +#define NUM_OBJECTS (256-JSW_OBJCNT) /* 83 */ + +unsigned char objloc[NUM_OBJECTS*2] = { + 0xf2,0xf2,0xf2,0xf2,0xf8,0xf8,0xf8,0xf8,0xeb,0xeb, + 0xeb,0xeb,0xdf,0xd2,0x4f,0x50,0x51,0xfc,0xfb,0xfa, + 0x65,0x65,0xe5,0x5b,0xf9,0xfa,0x5c,0xe2,0x59,0x59, + 0xd9,0xd9,0xf3,0x56,0xd6,0x68,0xcc,0x4c,0x4c,0xc9, + 0x47,0x6e,0x6e,0x66,0xd5,0xd5,0xd5,0xd5,0xd5,0x4e, + 0x4a,0xd5,0xd5,0x4e,0x4d,0x4d,0x6c,0x53,0xc3,0xc3, + 0xc3,0x71,0x71,0x71,0xf1,0xf1,0xf1,0x40,0x40,0x40, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x42, + 0xdd,0xde,0xe1, + // rest of XPOS/YPOS is high in the table at 0xA5A0 + 0x19,0x2a,0x34,0x38,0xc9,0xb3,0x7b,0x46,0x1f,0x19, + 0x1c,0x16,0x64,0x99,0x9f,0x9f,0x9f,0x1a,0xcd,0xb6, + 0x3c,0xd0,0xa6,0xdd,0x37,0xb6,0x7a,0x22,0x43,0xa8, + 0x06,0x66,0xc7,0xc9,0x22,0xf3,0xb3,0x70,0x96,0x65, + 0xef,0x6a,0x9d,0x49,0x91,0x92,0x94,0x96,0x97,0x5a, + 0x8b,0x99,0x9b,0x7a,0xc2,0x25,0x92,0x62,0x61,0x44, + 0x67,0x9c,0xe4,0xfc,0x42,0x5c,0xa4,0x93,0x94,0x95, + 0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x16, + 0x3a,0xc8,0xb7 +}; + + + + +STATIC void draw_object(unsigned offset) +{ + unsigned char *fb = &framebuffer[ATTRBUFFER_OFFSET_TO_FRAMEBUFFER_OFFSET(offset)]; + pallete[offset] = ( pallete[offset] & 0xf8 ) | // Lose INK + (TICKER & 0x3) + 0x3; + PRMATRIX( &ROOM.object[0], fb ); // Draw it. +} + +STATIC void CHKOBJECTS() +{ + unsigned char *objptr = &objloc[0]; + unsigned i; + for (i=NUM_OBJECTS;i!=0;i--) { + unsigned C = *objptr & 0x7F; // Top bit is Y position bit 4. + + if (C==(HERE|0x40)) { // Fake a non-collected bit ? + + // Yes. There's an object here + + unsigned off = (*objptr + *objptr) & 0x100; // Get bit into *512 position + + off+= objptr[NUM_OBJECTS]; // Add in X/Y extra position + + unsigned char *pltr = &pallete[off]; // Get extra x/y pos + if ( (*pltr & 0x7) == 0x7) { + // Willy collecting!!! + + *objptr &= 0xBF; // Clear 0x40 bit, meaning object is collected + + // This is indeed faster than using printf or similar + + pltr = (unsigned char*)&COLLECTED[2]; + do { + *pltr = *pltr+1; + if (*pltr<='9') { + break; + } + *pltr='0'; + pltr--; + } while (1); + + // Update COLLECTED on screen + + PRMESSAGE( &framebuffer[19*COLUMNS*8 + 16], (char*)COLLECTED, 3); + + } else { + draw_object(off); + } + } + objptr++; + } +} diff --git a/jsw/smallfs/JSW b/jsw/smallfs/JSW new file mode 100644 index 0000000000000000000000000000000000000000..bc3d739f85ab17df5fe230a6f83ab443c6264690 GIT binary patch literal 65536 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 literal 0 HcmV?d00001 diff --git a/jsw/smallfs/ZPUINO b/jsw/smallfs/ZPUINO new file mode 100644 index 0000000000000000000000000000000000000000..34548ac6f0302d6f8de0fc303ba98ba8b4eec80a GIT binary patch literal 6912 zcmcIoy^b6=4CZ7Cr7%1xQ(Q8ykaCMd*4@F?zCs?s zSmh)2N6qbJogyrDG$KXuBSliPF(#t?%y0c`jO4o+n)EA0GNYb|L>6m`N)yKrOnn8% zdT+@UemuDl>Wd7#H|IJ-?i2!LIvXB%zcz)2Sbq{<9X-al(N9$LQ?|nUGRnIs>oBun zV#0L-)HY{n{|vu%m*%3I_+MB1vNh5B!Iv?tnH&CfNccnZxW*s;C;Ya@-!^Cb)|t*B zaF<(~$W8n$J_U0ZpMj=^KOF?G1x7LynQ`PcN9MV)@$R8|!obgsIy zK?BW|i9u-KATUsvCe0qn=?%un@lh%{|F7GZx5N1_mSW}>zKwq@OzHgdHSw3Msu(WH zSTSj!Q)ObXD(l$3b((qw9X`RYJ@&GFFXQjdH}KgO-&^_dHTaE&N3orqWD%mL1yx%t$%$DO>YX z^xHBsM6OTe4bKbe)7;+E`Q&%Xg&pgC10(hg%(pa19}z{Uz52wlejb79RrvG$FDbCB-xXza;`>QQ&Z+xl!k_p91LXRo z;w$zgHqM8T*8nl|ulRHSN9q`tmWzG1+DAUGuO@qshEn++`qRSv%lE^%@{|4|%}>!^ zm7#aze%_IFL(MguOWdDDfb*N>c)dQ~urDn~eGuyE7iPW&KW*?g`>ajtZH0vT)an=R zmu3I7I}Ws0UmQ2_$$5`lBjCx&VKvoiN)2zd9c;;I{=JfQ|k21QEW9|B3Kr2io9o=WpFJ zoAXzF-=JM)`>V@fU;BQ`{@tGcDlGX8V^v89{^pF7e+3PmXW&=$A=MYf=kxDceSy83 z@QXL+x^>2Sy5c&*o+tBI1KFo@EO5wfc&G?L$txpj9*ro(e|Gh6kM2T z_YU|WfWv~%d}R;rE5J{K7yQmK{#IZ3_Ie@Cm&MxNAxcLYrno)FZ?UZ+ zZGBm&*gRCKF{X$!Rq>B=hab|j3wCll#_#zi1cxXExUe~xz6*#hgmL$ToZ=JEpd$gl ziT9E5!%2#d+g-x%Km9blXZ*c^O*E;!Zxd-mP^hUp4I-ksGFB>x_Q3zf#>X6g;MW=; zZ-C#p1^9;*H{r|pr^bhirvvCE03FeH4(Lex2iaTk?->8%FnDDE@LLZbd^3D}N9TLS z-<_W(n(*JXi8O-uI_gflh;(dzQ7R_h&HUmTezV0t`d|mo_(v2*_ydR(;p*$@%gXq_ zr8{8pKRLx_{Strl5a74txFTJgi3KbD2UuK1sF z{Gmw+#64YrA6@i5zNR~~TK_@gf6PR$veeyfuk#!D&(VB11OH@s1)Z%p#9dWGA!zZz@23