Spuzzle - My First Game

Miscellaneous Forums/Blitz Showcase/Spuzzle - My First Game

That's right, I've been using Blitz3D for years and this is my first game (written in Blitzmax).
I originally started this so I could write a tutorial on how to make a simple Blitzmax game, but I realised I wasn't sure what I was doing when I began so I just finished it and may get around to writing a tutorial for it sometime.

It's a (very) simple sliding puzzle game.

Rem
Spuzzle, a simple sliding tile, puzzle game based on the old plastic toys I used to have as a kid
EndRem
Strict

Graphics 800,600,0 ' Windowed mode, 800x600

SeedRnd(MilliSecs())


Type TPoint
	Field X
	Field Y
End Type


Type TSpuzzleBoard
		
	Field Width : Int
	Field Height : Int
	Field TileWidth : Int
	Field TileHeight : Int
	Field Board : Int[][] 'an array of arrays, because we need the board size to be dynamically resized and 
						'you cannot use slices to dynamically resize multi-dimensional arrays.
	Field img : TImage
	
	Field Target:Int
	Field MoveCount:Int
	Field Difficulty:Int

	Field BlankPos:TPoint
	Field LastMove:TPoint
	Field LastTileMoved:Int = -1
	

	Rem
		Moves the selected piece if possible
		'returns >0 if successful, 0 if not
	EndRem
	Method MoveTile:Int(X:Int,Y:Int)	'no actual need to declare the parameter type as int since it is the default, 
								  			'but this helps prevent confusion
		Rem
			check that the specified tile can move, if it can, 
			then move it and return true else return false
		EndRem
		
		Select CanTileMove(X,Y)
			Case 1
				LastTileMoved = Board[X][Y]
				Board[X][Y-1] = Board[X][Y]
				Board[X][Y] = 0
		
				BlankPos.X = x
				BlankPos.Y = y
				
				?debug
				'	Print "BP:"+BlankPos.X + ", "+BlankPos.Y
				?
				Return 1
			Case 2
				LastTileMoved = Board[X][Y]
				Board[X][Y+1] = Board[X][Y]
				Board[X][Y] = 0
		
				BlankPos.X = x
				BlankPos.Y = y
				?debug
				'	Print "BP:"+BlankPos.X + ", "+BlankPos.Y
				?
				Return 2
			Case 3
				LastTileMoved = Board[X][Y]
				Board[X-1][Y] = Board[X][Y]
				Board[X][Y] = 0
					
				BlankPos.X = x
				BlankPos.Y = y
				?debug
				'	Print "BP:"+BlankPos.X + ", "+BlankPos.Y
				?
				Return 3
			Case 4	
				LastTileMoved = Board[X][Y]
				Board[X+1][Y] = Board[X][Y]
				Board[X][Y] = 0
						
				BlankPos.X = x
				BlankPos.Y = y
				?debug
				'	Print "BP:"+BlankPos.X + ", "+BlankPos.Y	
				?
				Return 4
			Default
				Return 0
		End Select

		
	End Method

	Rem
		Returns 0 if the tile can't move, nonzero if it can
		the nonzero value represents the direction that the tile can move
		1 up
		2 down
		3 left
		4 right
		
		An individual tile should only be able to at most move in one direction
	EndRem
	Method CanTileMove:Int(X:Int,Y:Int)
		'check up
		If Y > 0 Then'if not at top edge
			If Board[x][y-1] = 0 Then Return 1
		EndIf
		
		'check down
		If Y < Len(Board)-1 Then ' if not at the bottom edge
			If Board[x][y+1] = 0 Then Return 2
		EndIf
		
		'check left
		If X > 0 Then 'if not at the left edge
			If Board[x-1][y] = 0 Then Return 3
		EndIf
		
		'check right
		If X < Len(Board)-1 Then 'if not at the right edge
			If Board[x+1][y] = 0 Then Return 4
		EndIf
	
		Return 0
	End Method

	Rem
		Use Shuffle to randomize the board (note that it will mix it up by moving the 
		tiles around so that the solution is valid) the difficulty will be used to 
		determine how many times the pieces are moved
	End Rem
	Method Shuffle(Diff: Int, Speed:Int = 500, SlowMo:Int=False)'if SlowMo is true then display each move
		Local CurrentMove:TPoint 
		Local m:Int
		CurrentMove = New TPoint

		If Diff < 1 Then Diff= 1 'can't have a value less than 1
		
		Difficulty = Diff

		For m = 0 To Difficulty*5
			
			'CurrentMove = GetMove()
			 'make sure we are not doing the last move
			CurrentMove = GetMove()

			'If CurrentMove.X = -1 Or CurrentMove.Y = -1 Then DebugStop

			LastMove.X = CurrentMove.X
			LastMove.Y = CurrentMove.Y

			MoveTile(CurrentMove.X, CurrentMove.Y)
			Target:+1
			If SlowMo Then
				Draw()
				Flip
				Cls
				Delay Speed
			EndIf
		Next
	End Method
	

	Method GetMove:TPoint()
		Rem
			Each element represents a direction
			0=up 1=down 2=left 3=right
			If it contains a TPoint value with values above zero
			then the tile in that direction can move
		EndRem
		Local CanMove:Int[4]
		Local Result:TPoint = New TPoint
		


		If BlankPos.Y > 0 Then		
			If (Board[BlankPos.X][BlankPos.Y - 1]) And (Board[BlankPos.X][BlankPos.Y - 1] <> LastTileMoved) Then 
				CanMove[0] = 1
			EndIf
		EndIf

		If BlankPos.Y < Height-1 Then
			If (Board[BlankPos.X][BlankPos.Y + 1]) And (Board[BlankPos.X][BlankPos.Y + 1] <> LastTileMoved) Then 
				CanMove[1] = 1
			EndIf
		EndIf
		
		If BlankPos.X > 0 Then
			If (Board[BlankPos.X - 1][BlankPos.Y]) And (Board[BlankPos.X - 1][BlankPos.Y] <> LastTileMoved) Then 
				CanMove[2] = 1
			EndIf
		EndIf

		If BlankPos.X < Width-1 Then
			If (Board[BlankPos.X + 1][BlankPos.Y]) And (Board[BlankPos.X + 1][BlankPos.Y] <> LastTileMoved) Then 
				CanMove[3] = 1
			EndIf
		EndIf

		Local rn = Rand(0,3)
		
		Local GotMove = False

		While Not GotMove
				rn = Rand(0,3)
				If CanMove[rn] = 1 Then GotMove = True
		Wend
			
		Select rn
			Case 0
				Result.X = BlankPos.X
				Result.Y = BlankPos.Y-1
			Case 1
				Result.X = BlankPos.X
				Result.Y = BlankPos.Y+1
				
			Case 2
				Result.X = BlankPos.X-1
				Result.Y = BlankPos.Y
				
			Case 3
				Result.X = BlankPos.X+1
				Result.Y = BlankPos.Y
				
		End Select
		
		Return Result
	End Method


	Rem
		Call reset() to put the tiles back in the starting position
	EndRem
	Method Reset()
		Local X:Int,Y:Int
		Local Num:Int = 0

		For X = 0 To Width-1
			For Y = 0 To Height - 1
				num :+ 1 'add 1 to num
				Board[Y][X] = num
			Next
		Next
		'remove the last piece from the board
		Board[Width-1][Height-1] = 0
		MoveCount = 0
		Target = 0
		BlankPos.X = 4
		BlankPos.Y = 4
	End Method
	
	Rem
		Draw the tiles to the screen
	EndRem
	Method Draw()
		Local X:Int,Y:Int

		For X = 0 To Width - 1
			For Y = 0 To Height - 1
				If Board[X][Y] > 0 Then 'loop through each element in the board array and draw the appropriate image
					DrawImage(img, X*TileWidth, Y*TileHeight, Board[X][Y]-1)'because we are using an animated image, we can use the number stored in each element as a frame number
				EndIf
			Next
		Next
		DrawText("Moves:" + MoveCount + "  Target: " + Target, 0,400) 
	End Method
	
	
	Method CheckInput()
		If MouseDown(1) Then 
		Local mx:Int = MouseX()
		Local my:Int = MouseY()
			If mx < (Width * TileWidth) And mx > 0 Then
				If my < (Height * TileHeight) And my > 0 'if mouse is over tiles
				
					If MoveTile(mx / TileWidth, my / TileHeight) Then MoveCount:+1
					
				EndIf
			EndIf
		EndIf
	End Method
	
	
	Method CheckWin:Int()
		'if all the tiles are in the correct order, then we've won
		'return 0 if false, 1 if true
		Local X:Int
		Local Y:Int
		Local count:Int

		If BlankPos.X < Width-1 Or BlankPos.Y < Height-1 Then Return 0	'if the blank position is not bottom right 
																				'then we cannot possibly have won, so exit
		For X = 0 To Width - 1
			For Y = 0 To Height - 1
				Count :+ 1
				If count < Width*Height Then 
					If Board[Y][X] <> Count Then Return 0
				EndIf
			Next
		Next

		Return 1
		
	End Method


	Method CalcScore:Int()
		Return (((Difficulty * Target) - MoveCount)*10) + (Difficulty * Difficulty )
	End Method
		
	
	Function Create:TSpuzzleBoard(Width:Int, Height:Int, ImageFile:String)
		Local brd:TSpuzzleBoard = New TSpuzzleBoard
		Local tempImg:TImage = LoadImage(ImageFile, DYNAMICIMAGE)
		Local _W:Int = ImageWidth(tempImg)
		Local _H:Int = ImageHeight(tempImg)
		Local Num:Int = 0
		Local X:Int
		Local Y:Int

			brd.TileWidth =  _W/Width
			brd.TileHeight = _H / Height
			brd.Width = Width
			brd.Height = Height
			
			brd.Board = brd.Board[..Height]
			For Y = 0 To Len(brd.Board)-1
				brd.Board[Y] = brd.Board[Y][..Width]
			Next
			brd.LastMove = New TPoint
				brd.LastMove.X = -1
				brd.LastMove.Y = -1
			brd.LastTileMoved = -1
			brd.BlankPos = New TPoint
			brd.Reset() 'reset the board pieces

			brd.img = LoadAnimImage(ImageFile, brd.TileWidth, brd.TileHeight, 0, Width*Height)
			
		tempImg = Null
		FlushMem

		Return brd
		
	End Function
	
End Type


Global GameEnded = False
Global GameWon = False

Global Level1:TSpuzzleBoard = TSpuzzleBoard.Create(5,5,"images/numbers.png")

'SET THE DIFFICULTY HERE (First parameter)
Level1.Shuffle(4,100,True)


'MAIN LOOP
While Not GameEnded

If Not GameWon Then
	Level1.CheckInput() 'Check for user input


	If Level1.CheckWin() Then 'check to see if we've won yet
		GameWon = True
	EndIf
Else
	DrawText("You have won!",0,420)
	DrawText("Score:"+Level1.CalcScore(),0,440)
	Delay 500
	Level1.Board[Level1.Height-1][Level1.Width-1] = Level1.Width * Level1.Height
EndIf

Level1.Draw() 'tell our instance of the board to draw itself using the draw method

'use escape to end the game for now
If KeyDown(KEY_ESCAPE) Then GameEnded = True

Flip
Cls

FlushMem

Wend
'END OF MAIN LOOP

Level1 = Null
FlushMem

End


image used

is there a max to plus converter for the lazy?

Nope, not that I'm aware of anyway.

Perturb -- move FlushMem inside your While/Wend loop, as it needs to free up the memory allocated while it's in there, not when it ends.

Works nicely though -- and that's the first time I've *ever* completed one of those grid puzzles!

I thought flushmem only needed to be called when you release or null variables?

Works nicely though -- and that's the first time I've *ever* completed one of those grid puzzles!

Cool :) I decided to make one after I got a new mobile and it had a simple (insanely easy) sliding tile game on it.

The way I've done it, it should be easy enough to use any image with it (so long as the dimensions of the image are divisible by the width and height that the board is set to).

FlushMem is needed to clear up any memory allocated and no longer used or in scope -- temporary variables, internal stuff, as well as all objects no longer referenced. (This includes stuff inside functions and methods that are called from the same scope as FlushMem.)

You generally just have to place it in your game's main loop as it'll free up anything called from there. Leaving it out will lead to memory leakage and eventual slowdown/crashing.

ok :)

I've updated it to have flushmem, but also to put the last tile in on completion.

Great.. where do i buy it. =)

BTW.. i just bought a new chair... um.. thought you might want to know.