; Simple "Moon Lander" game.
;
; Randall Hyde
; 2/8/96
;
; This program is an example of a trivial little "moon lander"
; game that simulates a Lunar Module setting down on the Moon's
; surface.  At time T=0 the spacecraft's velocity is 1000 ft/sec
; downward, the craft has 1000 units of fuel, and the craft is
; 10,000 ft above the moon's surface.  The pilot (user) can 
; specify how much fuel to burn at each second.
;
; Note that all calculations are approximate since everything is
; done with integer arithmetic.


; Some important constants

InitialVelocity	=	1000
InitialDistance	=	10000
InitialFuel	=	250
MaxFuelBurn	=	25

MoonsGravity	=	5		;Approx 5 ft/sec/sec
AccPerUnitFuel	=	-5		;-5 ft/sec/sec for each fuel unit.


		.xlist
		include 	stdlib.a
		includelib	stdlib.lib
		.list

		.386			;Comment out these two statements
		option	segment:use16	; if you are not using an 80386.

dseg		segment	para public 'data'

; Current distance from the Moon's Surface:

CurDist		word	InitialDistance

; Current Velocity:

CurVel		word	InitialVelocity

; Total fuel left to burn:

FuelLeft	word	InitialFuel


; Amount of Fuel to use on current burn.

Fuel		word	?

; Distance travelled in the last second.

Dist		word	?

dseg		ends

cseg		segment	para public 'code'
		assume	cs:cseg, ds:dseg

; GETI-	Reads an integer variable from the user and returns its
;	its value in the AX register.  If the user entered garbage,
;	this code will make the user re-enter the value.

geti		textequ	<call _geti>
_geti		proc
		push	es
		push	di
		push	bx

; Read a string of characters from the user.
;
; Note that there are two (nested) loops here.  The outer loop
; (GetILp) repeats the getsm operation as long as the user 
; keeps entering an invalid number.  The innermost loop (ChkDigits)
; checks the individual characters in the input string to make
; sure they are all decimal digits.

GetILp:		getsm

; Check to see if this string contains any non-digit characters:
;
; while (([bx] >= '0') and ([bx] <= '9')  bx := bx + 1;
;
; Note the sneaky way of turning the while loop into a
; repeat..until loop.

		mov	bx, di		;Pointer to start of string.
		dec	bx
ChkDigits:	inc	bx
		mov	al, es:[bx]	;Fetch next character.
		IsDigit			;See if it's a decimal digit.
		je	ChkDigits	;Repeat if it is.

		cmp	al, 0		;At end of string?
		je	GotNumber

; Okay, we just ran into a non-digit character.  Complain and make
; the user reenter the value.

		free			;Free space malloc'd by getsm.
		print
		byte	cr,lf
		byte	"Illegal unsigned integer value, "
		byte	"please reenter.",cr,lf
		byte	"(no spaces, non-digit chars, etc.):",0
		jmp	GetILp


; Okay, ES:DI is pointing at something resembling a number.  Convert
; it to an integer.

GotNumber:	atoi
		free			;Free space malloc'd by getsm.

		pop	bx
		pop	di
		pop	es
		ret
_geti		endp





; InitGame-	Initializes global variables this game uses.

InitGame	proc
		mov	CurVel, InitialVelocity
		mov	CurDist, InitialDistance
		mov	FuelLeft, InitialFuel
		mov	Dist, 0
		ret
InitGame	endp


; DispStatus-	Displays important information for each
;		cycle of the game (a cycle is one second).

DispStatus	proc
		printf
		byte	cr,lf
		byte	"Distance from surface: %5d",cr,lf
		byte	"Current velocity:      %5d",cr,lf
		byte	"Fuel left:             %5d",cr,lf
		byte	lf
		byte	"Dist travelled in the last second: %d",cr,lf
		byte	lf,0
		dword	CurDist, CurVel, FuelLeft, Dist
		ret
DispStatus	endp


; GetFuel-	Reads an integer value representing the amount of fuel
;		to burn from the user and checks to see if this value
;		is reasonable.  A reasonable value must:
;
;		* Be an actual number (GETI handles this).
;		* Be greater than or equal to zero (no burning
;		  negative amounts of fuel, GETI handles this).
;		* Be less than MaxFuelBurn (any more than this and
;		  you have an explosion, not a burn).
;		* Be less than the fuel left in the Lunar Module.

GetFuel		proc
		push	ax

; Loop..endloop structure that reads an integer input and terminates
; if the input is reasonable.  It prints a message an repeats if
; the input is not reasonable.
;
; loop
;	get fuel;
;	if (fuel < MaxFuelBurn) then break;
;	print error message.
; endloop
;
; if (fuel > FuelLeft) then
;
;	fuel = fuelleft;
;	print appropriate message.
;
; endif

GetFuelLp:	print
		byte	"Enter amount of fuel to burn: ",0
		geti
		cmp	ax, MaxFuelBurn
		jbe	GoodFuel

		print
		byte	"The amount you've specified exceeds the "
		byte	"engine rating,", cr, lf
		byte	"please enter a smaller value",cr,lf,lf,0
		jmp	GetFuelLp

GoodFuel:	mov	Fuel, ax
		cmp	ax, FuelLeft
		jbe	HasEnough
		printf
		byte	"There are only %d units of fuel left.",cr,lf
		byte	"The Lunar module will burn this rather than %d"
		byte	cr,lf,0
		dword	FuelLeft, Fuel

		mov	ax, FuelLeft
		mov	Fuel, ax

HasEnough:	mov	ax, FuelLeft
		sub	ax, Fuel
		mov	FuelLeft, ax
		pop	ax
		ret
GetFuel		endp



; ComputeStatus-
;
;	This routine computes the new velocity and new distance based on the
;	current distance, current velocity, fuel burnt, and the moon's
;	gravity.  This routine is called for every "second" of flight time.
;
; note:
;
;	Distance Travelled = Acc*T*T/2 + Vel*T  (note: T=1, so it goes away).
;	Acc = MoonsGravity + Fuel * AccPerUnitFuel
;
;	New Velocity = Acc*T + Prev Velocity
;
; 	This code should really average these values over the one second
;	time period, but the simulation is so crude anyway, there's no
;	need to really bother.

ComputeStatus	proc
		push	ax
		push	bx
		push	dx

; First, compute the acceleration value based on the fuel burnt
; during this second (Acc = Moon's Gravity + Fuel * AccPerUnitFuel).

		mov	ax, Fuel		;Compute
		mov	dx, AccPerUnitFuel	; Fuel*AccPerUnitFuel
		imul	dx

		add	ax, MoonsGravity	;Add in Moon's gravity.
		mov	bx, ax			;Save Acc value.

; Now compute the new velocity (V=AT+V)

		add	ax, CurVel		;Compute new velocity
		mov	CurVel, ax

; Next, compute the distance travelled (D = 1/2 * A * T^2 + VT +D)

		sar	bx, 1			;Acc/2
		add	ax, bx			;Acc/2 + V (T=1!)
		mov	Dist, ax		;Distance Travelled.
		neg	ax
		add	CurDist, ax		;New distance.
		
		pop	dx
		pop	bx
		pop	ax
		ret
ComputeStatus	endp


; GetYorN-	Reads a yes or no answer from the user (Y, y, N, or n).
;		Returns the character read in the al register (Y or N,
;		converted to upper case if necessary).

GetYorN		proc
		getc
		ToUpper
		cmp	al, 'Y'
		je	GotIt
		cmp	al, 'N'
		jne	GetYorN
GotIt:		ret
GetYorN		endp

		


Main		proc
		mov	ax, dseg
		mov	ds, ax
		mov	es, ax
		meminit

MoonLoop:	print
		byte	cr,lf,lf
		byte	"Welcome to the moon lander game.",cr,lf,lf
		byte	"You must manuever your craft so that you touch"
		byte	"down at less than 10 ft/sec",cr,lf
		byte	"for a soft landing.",cr,lf,lf,0

		call	InitGame

; The following loop repeats while the distance to the surface is greater
; than zero.

WhileStillUp:	mov	ax, CurDist
		cmp	ax, 0
		jle	Landed

		call	DispStatus
		call	GetFuel
		call	ComputeStatus
		jmp	WhileStillUp

Landed:		cmp	CurVel, 10
		jle	SoftLanding

		printf
		byte	"Your current velocity is %d.",cr,lf
		byte	"That was just a little too fast.  However, as a "
		byte	"consolation prize,",cr,lf
		byte	"we will name the new crater you just created "
		byte	"after you.",cr,lf,0
		dword	CurVel

		jmp	TryAgain

SoftLanding:	printf
		byte	"Congrats!  You landed the Lunar Module safely at "
		byte	"%d ft/sec.",cr,lf
		byte	"You have %d units of fuel left.",cr,lf
		byte	"Good job!",cr,lf,0
		dword	CurVel, FuelLeft

TryAgain:	print
		byte	"Do you want to try again (Y/N)? ",0
		call	GetYorN
		cmp	al, 'Y'
		je	MoonLoop

		print
		byte	cr,lf
		byte	"Thanks for playing!  Come back to the moon again sometime"
		byte	cr,lf,lf,0
	

Quit:		ExitPgm			;DOS macro to quit program.
Main		endp

cseg		ends

sseg		segment	para stack 'stack'
stk		byte	1024 dup ("stack   ")
sseg		ends

zzzzzzseg	segment	para public 'zzzzzz'
LastBytes	byte	16 dup (?)
zzzzzzseg	ends
		end	Main
