Contributor: BRAD ZAVITSKY {$IFDEF DEBUG} {$A+,B-,D+,F-,G-,I-,K-,L-,N-,E-,P-,Q+,R+,S+,T-,V-,W+,X+,Y-} {$ELSE} {$A+,B-,D-,F-,G-,I-,K-,L-,N-,E-,P-,Q-,R-,S-,T-,V-,W+,X+,Y-} {$ENDIf} {************************************************} { } { SNAiL ViSiON Demo v1.00.00 } { Strange Logic Software <=> Brad Zavitsky } { All Rights Reserved (1995) } { } {************************************************} { | NOTES: \------- There are no known bugs. Some people have been wondering about computer games so-called AI, this is a demo of PAI (Psuedo Artificial Inteligence) Sorry, no graphics :-), this is just ascii. I have made most of the games settings constants for changing various things around. If compiling in G+ mode, change COMPSPEED accordingly the enemies go MUCH faster. This will even work on a 8088 in REAL TIME! It has been pretty optimized for speed and size, notice, it does not use any units, cut back in a ton of linking. SWAG use it allowed (that is really the goal) VERSIONS -- 1.00.00 : First public release. Since I first posted this in the PASCAL LESSONS confrence I have made MANY changes to make it more of a game/run faster/ and have more configurable settings. Est.. *OPERATING Speed is 200%-500% faster. * I do have a delay which slows things down to regulate speeds. } Program Snaildemo; {$M $400,0,0} Const Top = 3; {Specs of your screen -2/+2} Bottom = 22; {""} RtSide = 77; {""} LftSide = 3; {""} Version : string[7] = '1.00.00'; CompSpeed : word = 6; {Higher = easier|Even = Easier} MaxEnemy = 68; {Should greater or equal to NumEnemy} NumEnemy : word = 30; {Number of enemies} AI : Byte = 60; {random move chance} Rep : Byte = 3; {Energy replenish} JumpChance : Byte = 90; {chance to make a jump} BadScore : Integer = -5; {Happens when a jump is failed} BadEnergy : Integer = -75; {Happens after a jump is failed} MaxEnergy : Word = 5000; {Max amount of energy} MaxScore : Word = 65500; Drain : Word = 2; {Amount drained per keypress} StartingEnergy : Word = 200; {Amount of starting energy} Scost : Word = 2; {Shield Usage Cost, if half} {then energy wont go down unless moving} SNeed : Word = 10; {Energy needed mantain shields} StatUpDate : Byte = 5; {When to update stats} ENeed : Word = 2; {Energy needed to move} JNeed : Word = 100; {Energy needed for hyper jump} SnailMan : Char = '@'; {Our hero} Langolier : Char = '#'; {Bad Guys} SoundOn : Boolean = True; {Turn this off if you don't like noise} Type {Directions used by MOVE} Dirtype = (North, East, West, South); {These are actually player/enemy records, you could probally add such things as hitpoints pretty easily} CursorRec = Record X,Y:Byte; End; { All the possible enemies, I have personally gone up to 1000 w/out changing memory! } AllEnemy = array[1..MaxEnemy] of CursorRec; Var Dead : Boolean; {Gee...what could this mean} Round, {Used to regulate stats updates} Turn : Byte; {This regulates enemy movement} Temp : AllEnemy; {BadGuy location, just what snailman needs to avoid} Loc : CursorRec; {Snailmans Location} I : Integer; {All purpose integer} Len : Byte; {Stores length of previous string for status line} Score, { player score} Energy : integer; {players current energy} OneMs : Word; {Used by delays, DO NOT TOUCH } Ch : Char; {IO char} ShieldOn : Boolean; {True if shields are on} PlayAnother : Boolean; {Play another game?} Procedure CB;Inline($CD/$33); {Simulate a ^C} Procedure DelayOneMS; assembler; {Better delay for 1ms} asm PUSH CX { Save CX } MOV CX, OneMS { Loop count into CX } @1: LOOP @1 { Wait one millisecond } POP CX { Restore CX } end; Procedure Delay(ms:Word); assembler; {better delay} asm MOV CX, ms JCXZ @2 @1: CALL DelayOneMS LOOP @1 @2: end; Procedure Calibrate_Delay; assembler; {makes delay accurate} asm MOV AX,40h MOV ES,AX MOV DI,6Ch { ES:DI is the low word of BIOS timer count } MOV OneMS,55 { Initial value for One MS's time } XOR DX,DX { DX = 0 } MOV AX,ES:[DI] { AX = low word of timer } @1: CMP AX,ES:[DI] { Keep looking at low word of timer } JE @1 { until its value changes... } MOV AX,ES:[DI] { ...then save it } @2: CAll DelayOneMs { Delay for a count of OneMS (55) } INC DX { Increment loop counter } CMP AX,ES:[DI] { Keep looping until the low word } JE @2 { of the timer count changes again } MOV OneMS, DX { DX has new OneMS } end; Procedure Beep(Hz, MS:Word); assembler; { Make the Sound at Frequency Hz for MS milliseconds } ASM MOV BX,Hz MOV AX,34DDH MOV DX,0012H CMP DX,BX JNC @Stop DIV BX MOV BX,AX IN AL,61H TEST AL,3 JNZ @99 OR AL,3 OUT 61H,AL MOV AL,0B6H OUT 43H,AL @99: MOV AL,BL OUT 42H,AL MOV AL,BH OUT 42H,AL @Stop: {$IFOPT G+} PUSH MS {$ELSE } MOV AX, MS { push delay time } PUSH AX {$ENDIF } CALL Delay { and wait... } IN AL, $61 { Now turn off the speaker } AND AL, $FC OUT $61, AL end; Procedure BoundsBeep; assembler; {Means you are touching an enemy} asm {$IFOPT G+ } PUSH 1234 { Pass the Frequency } PUSH 10 { Pass the delay time } {$ELSE} MOV AX, 1234 { Pass the Frequency } PUSH AX MOV AX, 10 { Pass the delay time } PUSH AX {$ENDIF } CALL Beep end; Procedure ErrorBeep; assembler;{Means you have touched an enemy and died} asm {$IFOPT G+ } PUSH 800 { Pass the Frequency } PUSH 75 { Pass the delay time } {$ELSE} MOV AX, 800 { Pass the Frequency } PUSH AX MOV AX, 75 { Pass the delay time } PUSH AX {$ENDIF } CALL Beep end; Procedure AttentionBeep; assembler; {Status Update beep} asm {$IFOPT G+ } PUSH 660 { Pass the Frequency } PUSH 50 { Pass the delay time } {$ELSE} MOV AX, 660 { Pass the Frequency } PUSH AX MOV AX, 50 { Pass the delay time } PUSH AX {$ENDIF } CALL Beep end; Procedure WarpSound; {Attemped warp sound} Var I:Word; Begin For I:= 500 to 600 do Beep(I,10); End; Procedure WarpDown; {Completed warp sound} Var I:Word; Begin For I:= 600 downto 500 do Beep(I,10); Delay(200); Beep(1000,10); Delay(200); Beep(1000,10); End; Procedure FClr;Assembler; {ClrScr} Asm MOV AH,0Fh Int 10h MOV AH,0 Int 10h End; Procedure GotoXY(X,Y : Byte); Assembler; Asm MOV DH, Y { DH = Row (Y) } MOV DL, X { DL = Column (X) } DEC DH { Adjust For Zero-based Bios routines } DEC DL { Turbo Crt.GotoXY is 1-based } MOV BH,0 { Display page 0 } MOV AH,2 { Call For SET CURSOR POSITION } INT 10h end; Function Int2Str(Number : LongInt): String; Var Temp : String[64]; Begin Str(Number,Temp); Int2Str := Temp; End; Procedure SetXY(x,y:byte;var A:CursorRec); Begin If (X > 0) and (X < 80) then A.x := x; If (Y > 0) and (Y < 25) then A.y := y; End; Procedure ClearKeyBoard;{Fast key clearer} Begin ASM CLI End; MemW[$40:$1A] := MemW[$40:$1C]; ASM STI End; End; Procedure GoXY(A:CursorRec); {moves cursorrec to its position} Begin Gotoxy(a.x,a.y); End; Procedure HideCursor; Assembler; Asm MOV ax,$0100 MOV cx,$2607 INT $10 end; Procedure ShowCursor; Assembler; Asm MOV ax,$0100 MOV cx,$0506 INT $10 end; Function WhereX : Byte; Assembler; Asm MOV AH,3 {Ask For current cursor position} MOV BH,0 { On page 0 } INT 10h { Return inFormation in DX } INC DL { Bios Assumes Zero-based. Crt.WhereX Uses 1 based } MOV AL, DL { Return X position in AL For use in Byte Result } end; Function WhereY : Byte; Assembler; Asm MOV AH,3 {Ask For current cursor position} MOV BH,0 { On page 0 } INT 10h { Return inFormation in DX } INC DH { Bios Assumes Zero-based. Crt.WhereY Uses 1 based } MOV AL, DH { Return Y position in AL For use in Byte Result } end; Procedure GETXY(A:CursorRec); {set cursorrec} Begin A.x := WhereX; A.y := WhereY; End; Procedure StatusBeep; {Look up, status line has been updated} Begin AttentionBeep; Delay(50); AttentionBeep; End; Function Readkey:char;Inline($B4/$07/$CD/$21); function KeyPressed:boolean;assembler; asm mov ah,$B; int $21; and al,$FE; end; Procedure ClrBox(X1,Y1,X2,Y2:Byte); Var OldX :Byte; AnyBt:Byte; OldY :Byte; AnyBt2:Byte; Begin OldX := WhereX; OldY := WhereY; gotoxy(x1,y1); For Anybt :=1 to Y2 do begin For AnyBt2 :=1 to X2 do write(#0); gotoxy(X1,Y1+AnyBt); End{For Loop}; gotoxy(oldX,OldY); End; Procedure Status(S:String;Clear:Boolean;UseSound:Boolean); {Gives messages on first line} Begin If (Clear) and (SoundOn) and (UseSound) then StatusBeep; Gotoxy(1,1); If Clear then ClrBox(1,1,Len,1) else gotoxy(len,1); Write(S); If Clear then Len:= Length(S) else Len:= Len + Length(S)+1; inc(len); Goxy(Loc); End; Function P100(Percent:Word):Boolean; {Percentage 100} Begin P100 := False; If Random(100)+1 <= Percent then P100 := True; End; Procedure StatInit; {Set up status bar |not status line|} Begin gotoxy(1,2); Write('[ STATUS ] ENERGY: SHIELDS: SCORE:'); End; {The following procedure update the status bar} Procedure UpDateEnergy; Var i:Byte; Begin Gotoxy(21,2); For I:=1 to 5 do write(#32); Gotoxy(21,2); Write(Energy); Goxy(Loc); End; Procedure UpDateShields; Var i:Byte; Begin StatusBeep; Gotoxy(41,2); For I:=1 to 5 do write(#32); Gotoxy(41,2); Write(ShieldOn); Goxy(Loc); End; Procedure UpDateScore; Var i:Byte; Begin Gotoxy(59,2); For I:=1 to Length(int2str(Energy))+2 do write(#32); gotoxy(59,2); Write(Score); Goxy(Loc); End; Procedure EngageShields; {Change shield status} Begin ShieldOn := not ShieldOn; UpDateShields; End; procedure Firephasers(A:CursorRec); {Check for collisions} begin If (A.x = Loc.x) and (A.Y = Loc.Y) then begin BoundsBeep; GoXy(A); Write(Langolier); If not shieldOn then begin If SoundOn then ErrorBeep; Dead := True; End;{ShieldOn} end;{If Locs match} End;{Fire} Procedure CheckHits; {Check for collisions} Var I:word; Begin While not dead and (I <> NumEnemy) do For I:= 1 to NumEnemy do Firephasers(Temp[I]); End; Function Move(Dir:DirType;Var A:CursorRec;Ch:Char):Boolean; {Move player/enemies} Begin Move := True; Case Dir of North: Begin If A.Y <= top then Move := False else begin goxy(A); Write(#0); Dec(A.Y); GoXY(A); Write(Ch); End;{If wherey} End;{K_Up} South: Begin If A.Y >= bottom then Move := False else begin goxy(A); Write(#0); Inc(A.Y); GoXY(A); Write(ch); End;{If wherey} End;{K_Down} East: Begin If A.X >= rtside then Move := False else begin goxy(A); Write(#0); Inc(A.X); GoXY(A); Write(Ch); End;{If wherex} End;{K_Right} West: Begin If A.X <= lftside then Move := False else begin goxy(A); write(#0); Dec(A.X); GoXY(A); Write(Ch); End;{If wherex} End;{K_Left} End;{Case} CheckHits; End;{Move} Procedure Jump; {Hyper Jump} Begin Status('Attempting Jump...',True,False); If SoundOn then WarpSound; If Energy >= Jneed then begin If P100(JumpChance) then {If you don't fail...} begin Goxy(Loc); Write(#0); SetXy((random(rtside-lftside)+lftside+1),(random(bottom-top)+top+1) ,Loc); goxy(Loc); Write(snailman); Dec(Energy, Jneed); {Get rid of used energy} Status('successfull',false,True); If SoundOn then WarpDown; {make some noise} End Else Begin Delay(200); {Failed Warp Noise} Beep(1500,20); Delay(200); Beep(1500,20); Delay(200); Beep(1500,20); Delay(200); Beep(1500,20); Status('Failed',False,True); Energy := BadEnergy; {Pay the price of a blown engine} Score := BadScore; {""} End; End else Begin status('not enough energy!',false,True); Delay(200); Beep(1000,10); End; End; procedure Movefoes; {The enemy is on the move} Var I:Word; begin Turn := 0; {reset turns} For I:=1 to numenemy do Begin If Temp[I].X > Loc.X then Move(West,Temp[I],langolier) else If Temp[I].X < Loc.X then Move(East,Temp[I],langolier); If Temp[I].Y > Loc.Y then Move(North,Temp[I],langolier) else If Temp[I].Y < Loc.Y then Move(South,Temp[I],langolier); If P100(AI) then {do they move on their own?} begin case (random(4)+1) of 1: Move(North,Temp[I],langolier); 2: Move(South,Temp[I],langolier); 3: Move(West,Temp[I],langolier); 4: Move(East,Temp[I],langolier); End;{Case} End;{Begin} end;{for to do} {EnemySave;} end; procedure Addscore; {regulates energy use, this could use some work} begin if (energy < MaxEnergy) and (odd(turn)) then inc(energy,rep); if (score < MaxScore) and (turn = compspeed-1) then inc(score); end; procedure Playgame; {Let the games begin} Var i:Word; begin For I:=1 to numenemy do {set up starting positions} begin SetXy((random(rtside-lftside)+lftside+1),(random(bottom-top)+top+1) ,Temp[I]); goxy(Temp[I]); Write(langolier); end; SetXy(3,5,Loc); goxy(loc); Write(snailman); repeat {begin} While keypressed do {MUCH faster than "If Keyressed"} Begin Ch := readkey; If (CH = #0) and (ENergy > ENeed) then {a function key means they are moving} BEGIN Dec(Energy, Drain); Ch := Readkey; Case CH of { left } #75 : Move(West,Loc,snailman); { rite } #77 : Move(East,Loc,snailman); { Up } #72 : Move(North, Loc, snailman); { Down } #80 : Move(South, Loc,snailman); { PGup } #73 : Begin Move(North, Loc, snailman); Move(East,Loc,snailman); End; { PDdn } #81 : Begin Move(South, Loc,snailman); Move(East,Loc,snailman); End; { Home } #71 : Begin Move(North, Loc, snailman); Move(West,Loc,snailman); End; { End } #79 : Begin Move(South, Loc, snailman); Move(West,Loc,snailman); End; End;{Case} END ELSE Case Ch of 'Q','q' : Dead := True;{Quit} 'J','j' : Jump; {Jump} 'S','s' : EngageShields;{Engage/disEngage shields} 'P','p' : Begin Inc(Energy, Drain); {Reimburse energy} Status('Paused... press ',true,True); repeat until readkey = #13; Status('',True,True); End; #3 : CB; {^C} End;{case} End;{While} If (Energy < SNeed) and (ShieldOn) then Begin ShieldOn := False; UpDateShields; End; If ShieldOn then Dec(Energy, SCost); ClearKeyBoard; If Round = StatUpDate then Begin GoXy(Loc); Write(SnailMan); UpDateEnergy; UpDateScore; Round := 0; End; inc(Round); If turn >= compspeed then movefoes; inc(turn); addscore; Delay(100); {end} until dead; end; Procedure SayHi; {Internal Instructions} Begin Writeln('Welcome to SNAiL ViSiON -- The virtual snail network -- '); Writeln('and only on channel 3031. Tonight we bring you, once again,'); Writeln('SNAiL MAN! Can the not-so-brave-and-not-too-tough SNAiLMAN'); Writeln('save the day? Well, as you know, with ViRTUAL SNAiL REALiTY'); Writeln('you will decide. And just how do you win you ask? Well the'); Writeln('snail isn''t known for it''s ninja-like karate skills, so'); Writeln('you just have to run as only a snail can.'); Writeln(''); Writeln('Advice --'); Writeln(' When you here two beeps, look up, it means something has'); Writeln(' just been updated. Also, be carefull when using'); Writeln(' HyperJump,if you fail you loose energy and points'); Writeln(''); Writeln('Instructions --'); Writeln(' Arrow keys move you in corresponding directions.'); Writeln(' PgUp, PgDn, Home, and End move diagonaly.'); Writeln(' P - Pause Q - Commit Sucicide S - Engage Snail Shields'); Writeln(' J - Snail HyperJump!'); Writeln(''); Writeln('Symbols --'); Writeln(' ',SnailMan,' - Snailman ',Langolier,' - Langolier'); Writeln(''); Write(' [ ]'#8#8); Repeat until readkey = #13; Fclr; End; begin {main program} (***********************************************************************) Calibrate_Delay; Delay(0); PlayAnother := True; Repeat randomize; NumEnemy := Random(16)+15; Dead := False; Score := 0; Turn := 0; Fclr; SayHi; HideCursor; ClearKeyBoard; Energy := StartingEnergy; ShieldOn := False; StatInit; UpDateShields; (***********************************************************************) Status('Welcome to SNAiL ViSiON v'+version+' ...',True,False); Playgame; (***********************************************************************) ShowCursor; FCLR; {Not only clears the screen, but resets some things as well} Writeln('Score: ',Score); Write('Play again? (Y/n)'); Repeat Ch := UpCase(Readkey); Until (Ch = 'Y') or (CH = 'N'); If Ch = 'N' then playanother := False; Until not PlayAnother; Fclr; (***********************************************************************) end. :::