MS-DOS Assembly Tools User interface Games

The editor lives in the same source as Bedlam, and it's switched on by one word. In 1995 BEDEDIT.ASM was BEDLAM.ASM with that word changed:

_editflag	dw -1
The only line that differed between the game and the editor

BEDEDIT.ASM on this page is that complete source, the game and the editor. The BEDLAM.ASM on the Bedlam page has had the editor taken out.

With the flag set, start-up also checks for a Microsoft-compatible mouse driver, and instead of the intro and the game, the main routine runs the editor until Esc is pressed. Everything else is shared: the memory map, the interrupt hooks, the sprites, the levels and the drawing code are all the game's.

[CHECK: what the editor was for, and how far I meant to take it]

Three windows

The screen is a desktop with three windows on it: CHARACTERS, which shows the sprite sheet; MAP, which shows the level; and SPRITE EDITOR, which was going to be where sprites were drawn. The desktop is the intro screen's grey background, tiled.

Each window is a record of exactly 32 bytes, and the three records sit one after another. The labels for the first window's fields double as the field names for all of them: _window_x1 is the address of the first window's x1, so _window_x1[bx] is the x1 of whichever window starts at offset bx, which is 0, 32 or 64.

_worder		dw 32, 0, 64

_window_x1:
_charwin_x1	dw 10
_window_y1:
_charwin_y1	dw 13
_window_x2:
_charwin_x2	dw 277
_window_y2:
_charwin_y2	dw 162
_window_off1:
_charwin_off1	dw 10+13*320
_window_off2:
_charwin_off2	dw 277+162*320
_window_w:
_charwin_w	dw 268
_window_h:
_charwin_h	dw 150
_window_proc:
_charwin_proc	dw offset cs: charwin_proc
_window_len:
_charwin_len	db 10
_window_title:
_charwin_title	db 'CHARACTERS...'
The window order, and the first window's record: its corners, their screen offsets, its size, its handler and its title

_worder is the stacking order, front to back, as record offsets. It starts as 32, 0, 64: the map in front, then the characters, then the sprite editor.

Messages

Each window has a handler, and the editor talks to it with a message number in AX, the way Windows does. 0 means redraw yourself, and 10h means the left button went down, with the mouse position already made relative to the window in CX and DX. Room was left for button-up and right-button messages, but those lines are commented out.

A handler is called through its record, and like Bedlam's timer routines it has to start with two NOPs. The caller checks for 9090h at the address before jumping.

Drawing the screen

The editor redraws everything, every time round its loop: the desktop, then the three windows from back to front, then the mouse pointer, then a copy to the screen after the vertical retrace. Drawing back to front is all the overlapping needs. Whatever is drawn last is on top, so there's no clipping and no record of what's covering what.

The window furniture is built from a few routines. edit_box draws a raised box, edit_frame an outline in one colour, edit_rect a filled rectangle, and edit_button copies one of eight 9x9 button bitmaps: Close, Under, Left, Right, Up, Down, Resize and Blank. A title bar is a Close button, a raised box and an Under button. The title strings are in the records but never drawn.

The CHARACTERS window copies the sprite sheet straight out of the sprite segment, column by column. The MAP window draws the level at a quarter size, four pixels by four for each square, from a little table of 4x4 patterns, one per tile type. And then it calls the game's own rules:

  inc si
  add di, 4
  loop mapwin_draw_xloop

  pop cx
  pop di
  add di, 320*4
  dec dx
  jnz mapwin_draw_yloop


  call game_move
The end of the map window's drawing routine, which runs one tick of the game every time it draws (trimmed)

So the level in the map is alive: rocks fall, bats fly round their walls, and because the editor redraws as fast as the screen refreshes, it all runs a lot faster than the game does.

The mouse

The mouse driver does the hard part, through int 33h. Function 3 returns the position and the buttons. The driver's coordinates are twice the screen's, so the editor halves both to get pixels:

  mov ax, 0003h
  int 33h
  shr cx, 1
  shr dx, 1
  mov _mouse_b, bx
  mov _mouse_x, cx
  mov _mouse_y, dx
Reading the mouse position and buttons from the driver, and scaling the position to the screen

The driver's own pointer doesn't work with a back buffer, so the editor draws its own. The pointer is 13x20 pixels with an AND mask and an XOR mask, drawn the same way as Bedlam's sprites. Before drawing, it saves the pixels underneath, so mouse_undo can put them back. A shadow under the pointer was written and then commented out.

A left click goes to the front window first. If the click is inside its rectangle, its handler gets the message, if not the next window is tried, and if no window was hit the click was on the desktop:

    ;  Test top window first.

  mov bx, _worder[0]
  cmp cx, word ptr _window_x1[bx]
  jb edit_main_lbutton_notwin1
  cmp cx, word ptr _window_x2[bx]
  ja edit_main_lbutton_notwin1
  cmp dx, word ptr _window_y1[bx]
  jb edit_main_lbutton_notwin1
  cmp dx, word ptr _window_y2[bx]
  ja edit_main_lbutton_notwin1
Hit testing the front window: is the mouse inside its rectangle?

Dragging and stacking

A click on a title bar, between the two buttons, starts a drag. First the mouse is limited with int 33h functions 7 and 8, so that the pointer can only go where the window would still be fully on the screen. Then the drag loop reads the mouse, adds how far it moved to the window's corners, works out their screen offsets again, and redraws the whole screen, until the button is let go. Then the limit goes back to the whole screen.

A click anywhere else in a window brings it to the front. The window's handler looks for its own offset in _worder and swaps it one place forward, twice, so wherever it was it ends up first:

  cmp _worder[4], 0
  jne charwin_ldown_nosort1
  mov ax, _worder[2]
  mov _worder[4], ax
  mov _worder[2], 0
charwin_ldown_nosort1:
  cmp _worder[2], 0
  jne charwin_ldown_nosort2
  mov ax, _worder[0]
  mov _worder[2], ax
  mov _worder, 0
charwin_ldown_nosort2:
Bringing the CHARACTERS window, record 0, to the front of the stacking order

Each window has its own copy of this code, with its own offset in it.

What isn't finished

The windows open, drag and stack, and that's as far as it got. What's missing:

  • None of the buttons do anything. Close and Under are drawn on every title bar, and the scroll bar buttons on the CHARACTERS window and the two buttons on the map's info bar are just pictures.
  • Clicking in the map doesn't change the level, and clicking a sprite doesn't pick it.
  • The SPRITE EDITOR window is an empty box.
  • The right button is read but ignored.
  • There's no way to save, so nothing could be written back to BEDLAM.BED.
  • The window titles are never drawn.
  • The map runs the game while you edit, which would need a pause.

There's also an empty editor routine further down the source, with its own logon, main and logoff that each just return.

The command line

Twelve days later, on 30 June 1995, I started BEDPARAM.ASM, a separate program for trying out command-line switches. It lowercases the command tail, finds each slash, and matches the word after it against a table:

_params         db '?', 00h	;  Help.
_param_help	db 'help', 00h	;  Help.
_param_dev  	db 'dev=', 00h	;  Development mode.
_param_new	db 'new=', 00h	;  Create new .BED file.
_param_edit	db 'edit=', 00h	;  Edit existing .BED file.
_param_play	db 'play=', 00h	;  Play .BED file (by default BEDLAM.BED).
The switches BEDPARAM understands, with what each one was for

The comments describe one program for everything: /play= to play a .BED file, /edit= to edit one, and /new= to start a new one. Each switch only prints its name so far. In BEDLAM.ASM the call to it is there, commented out, next to a _devflag that nothing reads yet.

Source 2 files

  • BEDEDIT.ASM Assembly · 6414 lines · the game and the editor
  • BEDPARAM.ASM Assembly · 509 lines · the command-line test

Downloads