Millenipede

Miscellaneous Forums/Blitz Showcase/Millenipede

Hi folks. After a long break from the last major version, I've released an update to Millenipede, a remake inspired by Atari's Centipede.



Description:
Your aim is to zap all of the millenipedes on each wave while dodging spiders, snails and of course the millenipedes themselves. They’ll make their way down the screen until eventually they hit the bottom, at which point they will roam around the player area until you shoot them. Don’t tarry too long or the warden will make your life really difficult…

This new version features lots of modern stuff such as online highscores, powerups, statistics tracking and generally refined visuals and gameplay. Of course there were also some bugs fixed too :)

Homepage:
http://www.zolyx.co.uk/millenipede/

Downloads:
Windows: http://www.zolyx.co.uk/files/millenipede-120-win.zip
MacOS: http://www.zolyx.co.uk/files/millenipede-120-mac.dmg

Screenshots






Happy blasting!

Looks cool, I don't have a windows machine anymore (at least connected to the internet) but I wish I could try it!

Perhaps I'll put it on a USB drive to try... Like the retro styling.

I like it. nice remake.

Cool retro game, there.

Great game!

The screenshots don't do the gameplay justice. Try it.

Hey thanks. Lots of fun and well made.

wow thats fun!

Sadly the MacOS version doesn't run on PPC but the screens look nice.

This is really well presented, good one, and thanks for the mention in the credits.

Did you choose to display all sprites at Integer coords on purpose to avoid anti-aliasing to preserve the retro feel? If not then maybe consider using floating point coords for you next games so things will appear to move smoothly. For example on the credits screen as the text rises up it jiggles up and down due to the use of integer coords and timing which does not match the display Hz, floats would resolve this. This may also be why some of the centipedes seemed to move then jerk then move then jerk unless your timing code needs a little look at?

Anyway, integer niggles aside, great game - I look forward to seeing more!

Oh and I have that chip tune from the enter your name screen, it's great :-)

Thanks for the comments all!

Sorry taumel, yes, I forgot to mention that the 1.1.0 Mac package is Intel-only.

I hope that this will change at some point in the not-too-distant future - a friend is working to create an updated Mac package right now, but I can't promise whether it'll still be Intel or universal binaries.

EDIT: Must've been posting at the same time Grey :) Game-wise, yes it was a deliberate choice to keep things on integer coords. All the test machines I have access to use 60Hz LCD monitors, so I can't visualise exactly what you mean by the move-jerk-move phenomenon of the 'pedes... for 1.2.1 I'll take a look at smoothing some of this stuff out :)

Cool. I know it's not my PC because I can make stuff move smoothly, so it may be worth looking into. It's not a big deal but it may add that extra pro look.

Have to say I love this retro game. Good job, Prospero.

Dude... I'm playing this for a few hours now, I think!
Screw friggin' UT or Halo, LOL! This rocks! 8)

Love most of the music, toO! Excellent job altogether!!!

The "radar" powerup gets my vote for "The Most Useful Powerup" award, but "missile volley" definitely gets the "Coolest Powerup" award. :)

I love the 8 bit-ish explosion effects.

The Intel Mac version of Millenipede 1.2.0 is now available :)

Unfortunately there won't be a PowerPC version in the immediate future :( If anyone fancies volunteering for the job, let me know!

Very nice game :) Really nice style.

There is a bug which can be exploited to get high scores. The game slows down when the user's computer's CPU is overworked.
Have millenipede running is several windows until it's slowed down enough. Then it's easy to get 80000+ points, this is what gumowy kurczak did, before you deleted his world high score.
Another side of it is different computers run the game at different speeds.
Have you fixed this yet?

This is the first time the problem has been reported, so no, it is not fixed yet :) Thanks for reporting this!

I will at least investigate the "multiple instances" bug as a priority when 2009 rolls around. If someone can point me in the direction of a thread discussing a BMax way to prevent an app running more than once, I would be very grateful. I've just done some searches here but couldn't find anything.

Yes, it is possible for the game to slow down below the usual 60Hz refresh rate, and certainly I know of one significant optimisation that could reduce the CPU power Millenipede requires. I'll look into that as well.

As an ultimate fix, though, it's unlikely that I'll add delta timing any time soon, as I have other projects I want to work on.

Season's greetings! :)

I had to open it 20 times before seeing significant slowdown...

If someone can point me in the direction of a thread discussing a BMax way to prevent an app running more than once, I would be very grateful.


The easiest way: Have your program check the list of running processes upon startup. If it finds more than one copy of itself, exit.
(The other one that was already running will continue to run, and you'll still have that single instance)

Here's a piece of code that should help under Windows (Created by Fredborg)

' By Fredborg

SuperStrict

Import maxgui.drivers

Extern "win32"
	Function CreateToolhelp32Snapshot:Int(flags:Int, th32processid:Int) 
	Function Process32First:Int(snapshot:Int, entry:Byte Ptr)
	Function Process32Next:Int(snapshot:Int, entry:Byte Ptr)
	Function CloseHandle:Int(_Object:Int) 
EndExtern

Const TH32CS_SNAPPROCESS:Int = $2
Const INVALID_HANDLE_VALUE:Int = -1

Type TWinProc 

	Global _list:TList = New TList

	Field dwSize:Int = 296
	Field cntUsage:Int
	Field th32ProcessID:Int
	Field th32DefaultHeapID:Int
	Field th32ModuleID:Int
	Field cntThreads:Int
	Field th32ParentProcessID:Int
	Field pcProClassBase:Int
	Field dwFlags:Int
	Field szExeFile:String

	Field kids:TList = New TList

	Method New()
		_list.AddLast Self
	EndMethod

	Method Free()
		_list.Remove Self
	EndMethod

	Method ToString:String()

		Local ret:String 
		ret =  "Name    : " + szExeFile
		ret :+ "Usage   : " + cntUsage
		ret :+ "Proc ID : " + th32ProcessID
		ret :+ "Heap ID : " + th32DefaultHeapID
		ret :+ "Mod  ID : " + th32ModuleID
		ret :+ "Threads : " + cntThreads
		ret :+ "Parent  : " + th32ParentProcessID
		ret :+ "ClasBas : " + pcProClassBase
		ret :+ "Flags   : " + dwFlags
	
	EndMethod

	Function CreateFromBank:TWinProc( bank:TBank )
		
		Local p:TWinProc = New TWinProc
		p.dwSize 				= PeekInt(bank,0)
		p.cntUsage 				= PeekInt(bank,4)
		p.th32ProcessID 		= PeekInt(bank,8)
		p.th32DefaultHeapID 	= PeekInt(bank,12)
		p.th32ModuleID 			= PeekInt(bank,16)
		p.cntThreads 			= PeekInt(bank,20)
		p.th32ParentProcessID	= PeekInt(bank,24)
		p.pcProClassBase 		= PeekInt(bank,28)
		p.dwFlags 				= PeekInt(bank,32)
		
		Local offset:Int = 36
		While offset<p.dwSize-1
			If PeekByte(bank,offset)
				p.szExeFile :+ Chr(PeekByte(bank,offset))
			Else
				Exit
			EndIf
			offset :+ 1
		Wend
		
		Return p
	
	EndFunction

	Function GetProcesses:Int()
	
		_list.Clear()

		Local snap:Int = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)

		Local bank:TBank = CreateBank( 296 )
		PokeInt bank,0,296

		If snap <> INVALID_HANDLE_VALUE

			If Process32First(snap, BankBuf(bank))
				
				Local nextproc:Int 
			
				Repeat
			
					TWinProc.CreateFromBank( bank )
					nextproc = Process32Next (snap, BankBuf(bank))
							
				Until nextproc = 0
				
			EndIf
			
			'
			' Arrange so kids are attached etc
			For Local p:TWinProc = EachIn TWinProc._list
				ArrangeProcess(p)
			Next
			
			CloseHandle(snap)
				
			Return True		
		Else
			Return False
		EndIf
		
	End Function

	Function ArrangeProcess( p:TWinProc )
		For Local q:TWinProc = EachIn _list
			If p <> q
				If p.th32ProcessID = q.th32ParentProcessID
					p.kids.AddLast q
					_list.Remove q
					ArrangeProcess(q)
				EndIf
			EndIf
		Next
	EndFunction
	
	Function Find:TWinProc( name:String, list:TList = Null )
		
		If list = Null
			list = _list
		EndIf
		
		For Local p:TWinProc = EachIn list
			If p.szExeFile = name
				Return p
			EndIf
			
			Local ret:TWinProc = Find( name, p.kids )
			If ret
				Return ret
			EndIf
		Next
		
	EndFunction

	'
	' Count the number of times a process is running
	'
	Function Count:Int( name:String, list:TList = Null )
		
		Local cnt:Int
		
		If list = Null
			list = _list
		EndIf
		
		For Local p:TWinProc = EachIn list
			If p.szExeFile = name
				cnt :+ 1
			EndIf
			
			cnt :+ Count( name, p.kids )
		Next

		Return cnt
		
	EndFunction
	
End Type

' ---------------------------------------------------
' DEMO A, Check how many times a process is running..
' ---------------------------------------------------

TWinProc.GetProcesses()

Local file:String = "svchost.exe"

'
' Try with:
' file = stripdir(appfile)
'

If TWinProc.Find( file )
	Notify file+" is running "+TWinProc.Count( file )+" time(s)"
EndIf


' -----------------------------------------------------------------------------
' DEMO B, Show the processes
' -----------------------------------------------------------------------------

AppTitle = "Process Tree..."

Local window:TGadget = CreateWindow ("Process Tree...", 300, 200, 500, 350)

Local tree:TGadget = CreateTreeView (0, 0, ClientWidth (window), ClientHeight (window) - 30, window)
Local root:TGadget = TreeViewRoot (tree)
SetGadgetLayout tree, 1, 1, 1, 1

Local button:TGadget = CreateButton ("Refresh list", 0, ClientHeight (window) - 25, 150, 21, window)
SetGadgetLayout button, 1, 0, 0, 1

Local menu:TGadget = CreateMenu ("&File", 0, WindowMenu (window))
CreateMenu "&Refresh", 1, menu
CreateMenu "", 2, menu
CreateMenu "&About", 3, menu
CreateMenu "E&xit", 4, menu
UpdateWindowMenu window

FillProcessTree (root)

Repeat

	Select WaitEvent ()
	
		Case EVENT_WINDOWCLOSE
			End
		
		Case EVENT_MENUACTION
		
			Select EventData ()
				Case 1
					FreeGadget tree
					tree = CreateTreeView (0, 0, ClientWidth (window), ClientHeight (window) - 30, window)
					root = TreeViewRoot (tree)
					SetGadgetLayout tree, 1, 1, 1, 1
					FillProcessTree (root)

				Case 3
					Notify "Process Tree..." + Chr (10) + Chr (10) + "An amazing Hi-Toro production, public domain 2003."
					
				Case 4
					End

			End Select
			
		Case EVENT_GADGETACTION
		
			Select EventSource ()

				Case button
				
					FreeGadget tree
					tree = CreateTreeView (0, 0, ClientWidth (window), ClientHeight (window) - 30, window)
					root = TreeViewRoot (tree)
					SetGadgetLayout tree, 1, 1, 1, 1
					FillProcessTree (root)

			End Select
			
	End Select

Forever

' -----------------------------------------------------------------------------
' Fill treeview gadget...
' -----------------------------------------------------------------------------

Function FillProcessTree(root:TGadget)

	TWinProc.GetProcesses()

	For Local p:TWinProc = EachIn TWinProc._list
		InsertProcess(p,root)
	Next

End Function

Function InsertProcess(p:TWinProc,root:TGadget)
	
	Local node:TGadget = AddTreeViewNode( p.szExeFile, root)
		
	For Local q:TWinProc = EachIn p.kids	
		InsertProcess(q,node)
	Next
		
EndFunction


One of the 'demo' functions in this program is to tell you how many instances of svchost.exe are currently running in windows. Just replace that name with the name of your own .exe , and if the result is more than one when you check, exit your program.
(Of course for this to work you would need to make sure that your executable name is somewhat unique, and that there wouldn't reasonably be another program with the same name running on a system. Don't call it explorer.exe for example, or your program would always find Windows explorer already runnig and exit itself)

Or may a mutex helps. Compile and run twice...
It is independent of a certain "Name.exe" as people than may start to rename it?

SuperStrict 
Import MaxGui.Drivers 

Global Mutex:Int 

OnEnd ReleaseInstance 
If InitSingleInstance("Prog1") Then DebugLog "ok" Else Notify "Prog1 ALREADY EXISTS!";End 


Local Window1:TGadget = CreateWindow:TGadget("Window1",357,138,236,143,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE |WINDOW_STATUS |WINDOW_CLIENTCOORDS ) 
   Local Ende:TGadget = CreateButton:TGadget("End",75,48,75,23,Window1:TGadget,BUTTON_PUSH) 

Repeat 
   WaitEvent() 
   Select EventID() 
      Case EVENT_WINDOWCLOSE 
         Select EventSource() 
            Case Window1   Window1_WC( Window1:TGadget ) 
         End Select 

      Case EVENT_GADGETACTION 
         Select EventSource() 
            Case Ende   Ende_GA( Ende:TGadget ) 
         End Select 

   End Select 
Forever 

Function Window1_WC( Window:TGadget ) 
   DebugLog "Window Window1 wants to be closed" 
'   HideGadget( Window:TGadget ) 

   End 
End Function 

Function Ende_GA( Button:TGadget ) 
   DebugLog "Button End was pressed" 
   End 
End Function 

Function InitSingleInstance:Int( name:String) 
   DebugLog "Create singele instance" 
?Win32 
   Extern "Win32" 
      Const ERROR_ALREADY_EXISTS:Int = 183 
       
      Function CreateMutexW:Int( security:Byte Ptr, owner:Int, name$w) 
      Function GetLastError:Int() 
   EndExtern 
    
   Mutex = CreateMutexW( Null, True, name) 
   If (Not Mutex) Or (GetLastError() = ERROR_ALREADY_EXISTS) Then Return False 
? 
   Return True 
EndFunction 

Function ReleaseInstance() 
   DebugLog "Release Mutex" 
   Extern "Win32" 
      Function ReleaseMutex:Int( mutex:Int) 
   EndExtern 
    
   If Mutex Then 
      ReleaseMutex( Mutex) 
      Mutex = 0 
   EndIf 
EndFunction



A mutex would indeed be more fool-proof.

Thanks to you both, that's a big help :)

Erm, the problem occurs when the CPU is working hard. Having several millenipede exe running at the same time is just one of many ways to achieve slow down. Mutex won't solve the prob!
The correct way is to use the Bmax equivalent of the createtimer, wait timer pattern. Then instead of slowing down, the game will skip frames.

@Nat the Great
That's just a veiled way of boasting of your fast CPU. ;)

I know, meemoe, but preventing multiple instances is something that's needed anyway :)

At present Millenipede already uses a WaitTimer() on a 60Hz interval to draw each frame - fixed rate logic, if you like. There is currently no tweening / delta timing in the game which would solve the problem completely. This may or may not be easy to add, but since it isn't something I've done before, I'm a little wary of trying to learn the concept by plugging it into an existing (and fairly hackish) set of code ;)