Skool Daze

Want to talks about games you like, would like to see developed on the Oric, it's here.
User avatar
Chema
Game master
Posts: 3014
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Okay, here we go...

Chapter 5: Peeking inside the AI

The AI system in Skool Daze is very complex, as you surely imagined watching all those characters moving around the school with their own agenda and the many different situations that may occur; this is what gives the player this feeling of freedom which made this game famous in the first place.

In order to keep the same feeling and being close to the original, I have studied and tried to imitate the AI system in the original game. The “fine grain” details are quite complex as well as the implementation, full of tricks with the stack and many many entry points to each routine, for instance.

But the general schema is easier to explain, and that is what I would try to do here. The AI system consists of four different types of commands:

The Primary Command
The Primary Command states what the character must do from the top level of abstraction, for instance make a character go to a specific location, or flag a given event or find Eric.

At each lesson (time periods in which the game is divided), a command list is specified for each character (command_list_high/low fields in data.s). This command list is a script with primary commands the character has to perform in this period (if he is able to – when the lesson finishes, a new command list is loaded and started).

This is an example of the command list for a teacher who should teach in the Reading Room this lesson (extracted from data.s):

Code: Select all

	.byt SC_GOTO, D_LIBRARY_INT				; Go to the library
	.byt SC_GOTO, D_READING_DOORWAY			; Go to the entrance of the room
	.byt SC_RESTIFNOLESSON					; Restart until it is time to start the lesson
	.byt SC_FLAGEVENT, E_TEACHER_READING	; Signal that the teacher has arrived
	.byt SC_MSGSITDOWN						; Tell the kids to sit down
	.byt SC_GOTO, D_POS_READING1			; Go to the teacher position
	.byt SC_GOTO, D_POS_READING2			; Go to the teacher position 2
	.byt SC_DOCLASS							; Wipe the board & conduct the class
All the codes can be found in script.h (the D_* are two byte destinations in school coordinates).

And this one controls a little boy when he has class in the exam room:

Code: Select all

	.byt SC_GOTO, D_EXAM_ROOM				; Go to the exam room
	.byt SC_MOVEUNTIL, E_TEACHER_EXAM		; Wait until teacher arrives
	.byt SC_FINDSEAT						; Find a seat and sit down
	.byt SC_END								; Sit still
When a Primary Command is completed, the next one is loaded. This means: taking the next token in the command list, finding the pointer to the associated routine, store it in the character’s data space (cur_command_high/low) and jump to it. The associated routine may get the parameters from the command list or, at some point change the Primary Command pointer to another address (another entry point or even another subroutine). Here the dirty tricks start...

The routines associated with every command can be found in script.s (s_*). Usually their name matches the token’s name quite well. The table with routine addresses is stored in data.s

Whenever a command list finishes, the high byte of the pointer is set to zero.

Interruptible and Uninterruptible Subcommands

But the behavior of the character is not so simple. While executing any Primary Command in their command list, the character might be set to perform a Subcommand. Most times this is because the most complex Primary Commands use Subcommands in their implementation, but also for other reasons. There are two types of subcommands: those which can be interrupted by another action and those which cannot.

Examples of Interruptible Subcommands are guide a character up or down a staircase or make a character speak. The character data space contains a pointer to the interruptible Subcommand routine which is being executed in i_subcom_low/high. If the high byte is zero, no interruptible subcommand is present. Implementations of those routines can be found in script.s (s_isc_* routines).

A final word on why these are interruptible. Imagine Einstein is grassing on you in class. You can punch him or hit him with your catapult and he will fall down (nice!). When he gets up again and sits down, he will continue where he was interrupted, though.

Examples of Uninterruptible Subcommands are those who control the travel of a catapult pellet, or those who make Angelface throw a punch, or those dealing with characters that have been knocked down. These routines are named s_usc_* in script.s and, of course, each character data space contains a pointer to the current one uni_subcom_high/low, where the high byte is zero if no subcommand is present.

Most of the routines check if the character has an uninterruptible subcommand in execution and avoid interrupting it.

Continual Subcommands

Yes, there is yet another class of subcommands, but I think they are better explained in a different section. The Continual Subcommands are a second class of interruptible subcommands which run in parallel with them (sort of). The best way to explain why they are there is with a clear example: the one which checks if Angelface is touching Eric (when the former has mumps) or the one which makes Angelface hit other kids now and then. Such a Continual Subcommand is placed in cont_subcom_low/high in the character data space by the Primary Command SC_SETCONTSUB, and they are executed while the character is doing other tasks. Implementations in script.s (csc_* routines).

Take a look at the command list associated to Angelface when he has to go to the map room in this lesson:

Code: Select all

	.byt SC_SETCONTSUB, CS_HITNOWTHEN		; Put a command making Angelface hit now & then
	.byt SC_GOTO, D_MAP_ROOM				; Go to the reading room
	.byt SC_SETCONTSUB, CS_HITNOWTHEN		; Put a command making Angelface hit now & then
	.byt SC_MOVEUNTIL, E_TEACHER_MAP		; Move about until the the teacher arrives
	.byt SC_FINDSEAT						; Find a seat and sit down
	.byt SC_END								; Sit still
Yeah, we have our system implemented. Now we need to make it work, we are implementing some kind of what we called back in the Windows 3.11 era “cooperative multitasking”. That is, loop through the characters (processes) and execute the code that needs to be, using the pointers and variables (there are quite a few) stored in the character data space (which is the equivalent to the process context), until the code returns to the control loop (thus it is cooperative: do something quick and return control, or you’ll use all the CPU for yourself).

In Skool Daze the main loop takes the following steps:

1/ If there is an uninterruptible subcommand routine address, jump (jmp) to it
2/ If there is a continual subcommand routine call it (using a jsr so control then gets to step 3)
3/ If there is an interruptible subcommand, jump to it
4/ Restart the command list if a flag in the character’s data indicates so
5/ If there is a primary command routine, jump to it
6/ Remove the continual subcommand if present (we have finished the primary command here, so the associated continual subcommand must be finished too)
7/ Collect the next primary command routine address from the command list, and jump to it

With some slight variations, this is the way it is done in the speccy version of the game.

If you check engine.s, this main loop is:

Code: Select all

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This routine mimics the Speccy version, which apparently
; checks 3 characters in case they need moving at each
; frame. Seems too slow on Oric?
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

move_chars
.(
	; We will check 7 characters
	lda #6
	sta tmp6
loop
	ldx last_char_moved
	jsr move_char	 
	dec tmp6
	bne loop

	ldx last_char_moved
+move_char
	cpx #20
	bne notend

	; if in demo mode (set by Self-modifying code)
+demo_ff_00
	ldx #0	; Don't move Eric here
notend
	inx
	stx last_char_moved


Up to here pretty straightforward, except for the self-modifying code used to switch between demo and play. Now we need to check if the character must be moved (they have different speeds and such things). This is quite tricky because it uses a jsr which returns here only if the character must move and gets rid of the context and returns to the previous caller if not (issuing a pla:pla:rts).

Code: Select all

	; Reg X contains the character ID. This is usually kept
	; unchanged. The next jsr is a bit tricky as it checks if
	; a character must move and, if not, does pla:pla:rts

	jsr must_move


Time to move a character then, perform the 7 steps specified above. Quite easy to follow:

Code: Select all

; If we arrive here the character must be moved, then we take the 7 steps.
+unint_subcommand
	;; Step 1
	lda uni_subcom_high,x
	beq nounisubcommand
	sta tmp0+1
	lda uni_subcom_low,x
	sta tmp0
	jmp (tmp0)
nounisubcommand

+cont_subcommand
	;; Step 2
	lda cont_subcom_high,x
	beq nocontsubcommand
	sta smc_cont_jump+2
	lda cont_subcom_low,x
	sta smc_cont_jump+1
smc_cont_jump
	jsr $dead	; this is a jsr, not a jmp as the rest...
nocontsubcommand


+int_subcommand
	;; Step 3
	lda i_subcom_high,x
	beq noisubcommand
	sta tmp0+1
	lda i_subcom_low,x
	sta tmp0
	jmp (tmp0)
noisubcommand

+check_reset_cl
	;; Step 4
	lda flags,x
	and #RESET_COMMAND_LIST
	beq no_reset
	lda flags,x
	and #(RESET_COMMAND_LIST^$ff) 
	sta flags,x
	lda #0
	sta cur_command_high,x
	sta pcommand,x
no_reset

+primary_command
	;; Step 5
	lda cur_command_high,x
	beq command_completed
	sta tmp0+1
	lda cur_command_low,x
	sta tmp0
	jmp (tmp0)
command_completed	

	;; Step 6
	lda #0
	sta cont_subcom_high,x

+next_command
	;; Step 7
	lda command_list_high,x
	beq nocommandlist
	sta tmp0+1
	lda command_list_low,x
	sta tmp0
	lda pcommand,x
	tay
	lda (tmp0),y	; Keep pointer prepared for the called routine
	tay
	lda command_high,y
	sta smc_command+2
	lda command_low,y
	sta smc_command+1
smc_command
	jmp $dead

nocommandlist
	rts
	
.)
Quite a nice system, isn’t it? Probably the most complex and wonderful AI system I have ever seen in these machines, but my experience is rather poor here. That is why I am doing all this: to learn.
Last edited by Chema on Thu Jun 28, 2012 7:38 pm, edited 1 time in total.
Antiriad2097
Flying Officer
Posts: 158
Joined: Tue May 09, 2006 9:42 pm
Location: Aberdeen, UK
Contact:

Post by Antiriad2097 »

A very interesting insight to the game and AI in general, thanks for taking the time to explain that Chema.
User avatar
Iapetus
Flying Officer
Posts: 135
Joined: Thu Mar 19, 2009 10:47 pm

Post by Iapetus »

Thank you Chema, the AI system is very interesting indeed.
User avatar
kamelito
Flying Officer
Posts: 182
Joined: Sun Jan 08, 2006 6:34 pm
Location: Nantes, France

Post by kamelito »

Chema,

Are you using this disassembly ?

http://pyskool.ca/disassemblies/skool_daze/

Author interview.
http://pixelatron.com/blog/skool-daze-f ... etrogamer/

Kamel
/kml
skype pseudo : kamelitoloveless
Antiriad2097
Flying Officer
Posts: 158
Joined: Tue May 09, 2006 9:42 pm
Location: Aberdeen, UK
Contact:

Post by Antiriad2097 »

Chema wrote:...to follow the information on this link:

http://pyskool.ca/disassemblies/skool_daze/

which is, by the way, a great source of information about the game internals I am using.
Quality interview that one. I don't know that Mark writes for RG any more.
User avatar
Chema
Game master
Posts: 3014
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Thanks for your comments. And yes, I am using that source of information, which is invaluable.

A lot of code is quite a direct translation from the z80 soruces to the 6502, though with some modifications wherever I think an alternative approach is better for the architecture of the latter.

I am still experimenting with colors and hunting bugs, and quickly approaching the memory limit. I need to think about using compression with the text to make some room for the sfx and the game ending (only bit missing is the actual opening of the safe). I'd like to include the music, even as a very basic tune (forget about using something like Wave, there is simply no room).

Has anybody experimented with producing simple tunes in a compact way? Something like storing the note and the duration and a very small player... Don't need anything like it being interrupt driven or ornaments or effects...

And, do you want me to explain further any detail I may have overlooked in my previous posts?
User avatar
Dbug
Site Admin
Posts: 4444
Joined: Fri Jan 06, 2006 10:00 pm
Location: Oslo, Norway
Contact:

Post by Dbug »

The simplest music player I can think of is the one in the Oric Atmos user manual. It's in BASIC but easy to port to anything else.
JamesD
Flight Lieutenant
Posts: 358
Joined: Tue Nov 07, 2006 7:38 am

Post by JamesD »

Chema wrote: Has anybody experimented with producing simple tunes in a compact way? Something like storing the note and the duration and a very small player... Don't need anything like it being interrupt driven or ornaments or effects...
I posted assembly source to fairly simple assembly player here:
http://forum.defence-force.org/viewtopic.php?t=193
But it's interrupt driven and it might not meet your compact requirement.
Songs are just values to dump to the sound chip registers and duration.
User avatar
Chema
Game master
Posts: 3014
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Ah thanks indeed JamesD. I think this player may suit my needs. I am not sure if I will be able to write any music as AY register values, but I will certainly try. And it should also be suitable for the basic sfx.

In fact I was tempted to make direct calls to the ROM sound routines (music) so a song could just be stored as a squence of notes and duration (and also it will match notes with frequencies), but that will prevent it to work on an Oric-1 unless the entry points are the same, which is somehting I don't know.

A lot of exploration needed in this area I guess...
JamesD
Flight Lieutenant
Posts: 358
Joined: Tue Nov 07, 2006 7:38 am

Post by JamesD »

Chema wrote:Ah thanks indeed JamesD. I think this player may suit my needs. I am not sure if I will be able to write any music as AY register values, but I will certainly try. And it should also be suitable for the basic sfx.

In fact I was tempted to make direct calls to the ROM sound routines (music) so a song could just be stored as a squence of notes and duration (and also it will match notes with frequencies), but that will prevent it to work on an Oric-1 unless the entry points are the same, which is somehting I don't know.

A lot of exploration needed in this area I guess...
The player really needs a tool to build the music. There may be one out there since it's just a conversion of a Z80 player, but I didn't find it.
User avatar
Chema
Game master
Posts: 3014
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Thanks JamesD. I will most probably use your idea at least for the sound effects. This should give me a bit more variety and make them sound by interrupts.

In the meantime I have coded the game original tune (Girls & Boys come out to play) along with a simple player (which is for now fat and clumsy, but works). It is quite simple, playing the song with just two channels and no nice effects, but I think it will work.

To make some room I also added compression to the text strings and I gained over 700 bytes, which I hope is enough to fit in what I need to finish this. Yeah, the compression ratio is not that good (a saving of over 40% in the best cases) and I had to add additional code to handle the compression in some specific cases, but I think I had no other choice.

I also have still some room in the holes of my fragmented memory map for the data (the music tune, which is over 90 bytes, is placed in one of those).

I am debating between using my own code for all this (which is what I am doing now): dumping an AY registers, converting between notes and pitch, etc... or directly calling the ROM. The latter will use much less space, but will prevent it from working on an Oric-1, unless the entry points are the same, or at least they are known, so I can build an Oric-1 and an Atmos version...

Will try to add some little sfx and also the code needed so the game can be finished (I recall there was a second tune in the game, btw). Then I could think of releasing a beta.

Cheers.
User avatar
Chema
Game master
Posts: 3014
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Okay, I have been working on this a bit more. I am trying to sort out a problem which appeared on the original Spectrum version with the rendering order of the sprites.

Maybe you recall the teacher being drawn over the chairs but seated Eric being drawn over the teacher. Also something similar happens with the stairs.

The game is 2D so no full solution is possible, but I am trying to find the best looking option.

I have uploaded a small vid with the current status of the game:
http://www.youtube.com/watch?v=nRrqAhyJ_uA

Please notice that sfx are still temptative as I am experimenting...

Enjoy!
User avatar
kamelito
Flying Officer
Posts: 182
Joined: Sun Jan 08, 2006 6:34 pm
Location: Nantes, France

Post by kamelito »

Chema wrote:Okay, I have been working on this a bit more. I am trying to sort out a problem which appeared on the original Spectrum version with the rendering order of the sprites.

Maybe you recall the teacher being drawn over the chairs but seated Eric being drawn over the teacher. Also something similar happens with the stairs.

The game is 2D so no full solution is possible, but I am trying to find the best looking option.

I have uploaded a small vid with the current status of the game:
http://www.youtube.com/watch?v=nRrqAhyJ_uA

Please notice that sfx are still temptative as I am experimenting...

Enjoy!
Quite nice! congrats.
I just compared it to a youtube zx spectrum version and it's pretty similar. The scrolling seems more smooth on the Speccy thought.
But again I didn't knew the Oric was capable of games like yours and Twi ones.
/kml
skype pseudo : kamelitoloveless
User avatar
Chema
Game master
Posts: 3014
Joined: Tue Jan 17, 2006 10:55 am
Location: Gijón, SPAIN
Contact:

Post by Chema »

Yep, the scrolling is quite faster in the speccy version. I guess the z80 beats the 6502 there...

Also you can compare with the c64 version, which has a much bigger visible area:
http://www.youtube.com/watch?v=wgwi9t-wf1o

which does not have the nice continuous scrolling, either.

In our favour I have solved most of the know bugs of the game :)
Antiriad2097
Flying Officer
Posts: 158
Joined: Tue May 09, 2006 9:42 pm
Location: Aberdeen, UK
Contact:

Post by Antiriad2097 »

I'm impressed. I think the scrolling is a nice compromise, it looks like a very accurate port. Another showcase game for the Oric to prove it could have been a contender.
Post Reply