Hollywood Blocks v. 0.1

Discuss any general programming issues here
Post Reply
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Hollywood Blocks v. 0.1

Post by jalih »

First test version available here

It's a source release. Sorry, my first "tetris game" and written in a hurry. Took about 2 hours... I will put a better version available soon in my friends server space.

Use cursor keys to controll falling blocks and "p" - key for pause.


Stil to be done:

Clean up drawing routines (currently a lot of numbers and almost no constants in code).

Blocks are are currently sliced by drawing a black grid over the play area. Cleaner approach would be to calculate all the block offsets directly.

Balance the gameplay (levels, speed).


As always, have fun...
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: Hollywood Blocks v. 0.1

Post by jalih »

Now available here

There is also a Windows binary.
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: Hollywood Blocks v. 0.1

Post by jalih »

I decided to write some "classes" to help cleaning up the code.

There are now helper "classes" for basic primitives like point and rectangle.


Let's look at point "class" as an example:

Code: Select all

point = {}

; xy = {  x, y  }
Function point:new(xy)
	Local p = CopyTable(xy)
	
	Function p:add(point)
		self.x = self.x + point.x
		self.y = self.y + point.y
	EndFunction

	Function p:sub(point)
		self.x = self.x - point.x
		self.y = self.y - point.y
	EndFunction

	Function p:mul(number)
		self.x = self.x * number
		self.y = self.y * number
	EndFunction
	
	Function p:divide(number)
		self.x = self.x / number
		self.y = self.y / number
	EndFunction
	
	
	Return(p)
	
EndFunction
Now it's easy to calculate from map coordinates to screen coordinates:

Code: Select all

Function piece:draw()
	SetFillStyle(#FILLCOLOR)

	Local i, v = NextItem(shapes.shapes[self.id].coords[self.rotation])
	While GetType(i) <> #NIL
		Local p = point:new(shapes.shapes[self.id].coords[self.rotation][i])
		p.x = p.x + self.x
		p.y = p.y + self.y
		p:mul(#BLOCKSIZE)
				
		Box( p.x, p.y, #BLOCKSIZE, #BLOCKSIZE, shapes.shapes[self.id].color)
		i, v = NextItem(shapes.shapes[self.id].coords[self.rotation], i)
	Wend
	
EndFunction
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: Hollywood Blocks v. 0.1

Post by jalih »

My basic primitive helper "class" is starting to shape up...

Currently it includes only point and rectangle "types".

Code: Select all

;
; Simple helper "classes"
;

; Point "class"
point = {}


Function point:new(xy)
	Local p = CopyTable(xy)
	
	Function p:add(point)
		Local pp = CopyTable(self)
		pp.x = pp.x + point.x
		pp.y = pp.y + point.y
		Return(pp)
	EndFunction

	Function p:sub(point)
		Local pp = CopyTable(self)
		pp.x = pp.x - point.x
		pp.y = pp.y - point.y
		Return(pp)
	EndFunction

	Function p:mul(number)
		Local pp = CopyTable(self)
		pp.x = pp.x * number
		pp.y = pp.y * number
		Return(pp)
	EndFunction
	
	Function p:divide(number)
		Local pp = CopyTable(self)
		pp.x = pp.x / number
		pp.y = pp.y / number
		Return(pp)
	EndFunction
	
	Function p:clone()
		Return(CopyTable(self))
	EndFunction

	
	
	Return(p)
	
EndFunction


; Rectangle "class"
rect = {}


Function rect:new(rectangle)
	Local r = CopyTable(rectangle)
	
	Function r:addpt(point)
		Local rr = CopyTable(self)
		rr.min.x = rr.min.x + point.x
		rr.min.y = rr.min.y + point.y
		rr.max.x = rr.max.x + point.x
		rr.max.y = rr.max.y + point.y
		Return(rr)
	EndFunction

	Function r:subpt(point)
		Local rr = CopyTable(self)
		rr.min.x = rr.min.x - point.x
		rr.min.y = rr.min.y - point.y
		rr.max.x = rr.max.x - point.x
		rr.max.y = rr.max.y - point.y
		Return(rr)
	EndFunction
	
	Function r:dx()
		Return(self.max.x - self.min.x)
	EndFunction
	
	Function r:dy()
		Return(self.max.y - self.min.y)
	EndFunction
	
	Function r:eq(rect)
		If self.min.x = rect.min.x And
		   self.min.y = rect.min.y And
		   self.max.x = rect.max.x And
		   self.max.y = rect.max.y
			Return(True)
		Else
			Return(False)
		EndIf

	EndFunction

	Function r:overlap(rect)
		If self.min.x > rect.min.x + (rect.max.x - rect.min.x) - 1 Or
		   self.min.y > rect.min.y + (rect.max.y - rect.min.y) - 1 Or
		   rect.min.x > self.min.x + self:dx() - 1 Or
		   rect.min.y > self.min.y + self:dy() - 1
			Return(False)
		Else
			Return(True)
		EndIf
	EndFunction
	
	Function r:clone()
		Return(CopyTable(self))
	EndFunction
	
	
	Return(r)
	
EndFunction
And here is a simple example demonstrating it's usage:

Code: Select all

;
; Langton's Ant example
;
;  By the way... We really should draw all the map cells just once but I will leave that as an exercise... ;-) 
;

; Width = (#MAPX * #CELLX) + ((#MAPX + #PAD) * #PAD)
; Height = (#MAPY * #CELLY) + ((#MAPY + #PAD) * #PAD)
@DISPLAY {Width = 551, Height = 551}


; Include game framework
@INCLUDE "game.hws"

; Include modules
@INCLUDE "primitives.hws"



; Some constants to help cleaning up code
Const #CELL_SIZE = 10

Const #PAD = 1

Const #MAPX = 50 
Const #MAPY = 50

Const #EMPTY = 0
Const #TRAIL = 1

Dim map[#MAPY][#MAPX]


; Setup
Function game.load()
	game:setUpdateRate(60)
	
	dirs = { { x = -1, y = 0 }, { x = 0, y = 1 }, { x = 1, y = 0 }, { x = 0, y = -1 } } 
	antdir = 0
	cellr = rect:new( { Min = { x = 0, y = 0 }, Max = { x = #CELL_SIZE, y = #CELL_SIZE } } )
	antmap = point:new( { x = #MAPX / 2, y = #MAPY / 2 } )
	antbuf = point:new( { x = antmap.x * #CELL_SIZE, y = antmap.y * #CELL_SIZE } )
	antbuf = antbuf:add( { x = antmap.x * #PAD + #PAD, y = antmap.y * #PAD + #PAD } )
	colors = { #WHITE, #BLACK }
EndFunction


; Draw
Function game.draw()
	draw_map()
	draw_rect(cellr:addpt(antbuf), #RED)
EndFunction


; Update
Function game.update(dt)
	If map[antmap.y][antmap.x] = #TRAIL
		map[antmap.y][antmap.x] = #EMPTY
		antdir = (antdir + 1) % 4
	Else
		map[antmap.y][antmap.x] = #TRAIL
		antdir = (antdir + 3) % 4
	EndIf
	
	antmap = antmap:add(dirs[antdir])
	antmap.x = (antmap.x + #MAPX) % #MAPX
	antmap.y = (antmap.y + #MAPY) % #MAPY
	
	antbuf = antmap:add( { x = antmap.x * #CELL_SIZE * #PAD + #PAD, y = antmap.y * #CELL_SIZE * #PAD + #PAD } )
	
EndFunction

;---------------------------------------------------------------------------------------------------------------------------------------------

Function draw_rect(rect, color)
	SetFillStyle(#FILLCOLOR)
	Box(rect.min.x, rect.min.y, rect:dx(), rect:dy(), color)
EndFunction


Function draw_map()
	For y = 0 To #MAPY - 1
		For x = 0 To #MAPX - 1
			p1 = point:new( { x = x * #CELL_SIZE, y = y * #CELL_SIZE } )
			p2 = { x = x * #PAD + #PAD, y = y * #PAD + #PAD }
			p = p1:add(p2)
			draw_rect(cellr:addpt(p), colors[map[y][x]])
		Next
	Next

EndFunction


; Go!
game:go()
You also need my game template:

Code: Select all

;
; Game template module: game.hws
;

game = {}
game.updateRate = 20 ; Default to 20 millisec = 50 fps.
game.dt = 0
game.fps = 0


; Game setup function for the user.
Function game.load()
	; Our game must override this dummy function.
EndFunction


; Game update function for the user
Function game.update(dt)
	; Our game must override this dummy function.
EndFunction


; Game draw frame function for the user
Function game.draw()
	; Our game must override this dummy function.   
EndFunction


; Our game framework init function. Calls just user game setup function for now
; and starts double buffering.
Function game.init()
	game.load()
	BeginDoubleBuffer()   
EndFunction


; Basic game loop function for our framework.
Function game.loop(dt)
	If game.state.event$ = "init" Or game.state.event$ = "update"
		game.state.event$ = "draw"
		If RawGet(game.state.states[game.state.state], "draw") Then game.state.states[game.state.state].draw()
		Flip()	
	EndIf

	If game.state.event$ = "draw"
		game.state.event$ = "update"
		If RawGet(game.state.states[game.state.state], "update") Then game.state.states[game.state.state].update(dt)	
	EndIf
EndFunction


; Set update rate for the game.
Function game:setUpdateRate(fps)
	self.updateRate = 1000 / fps
EndFunction


; Get the current fps-rate.
Function game:getFPS()
	Return(Round(1000 / (self.dt * 1000)))
EndFunction


; Runs the game, handles calling the game loop and timing.
Function game.run()
	StartTimer(1)
	StartTimer(2)

	Repeat
		; Try to lock the frame update rate to game.updateRate
		WaitTimer(1, game.updateRate)
		; Get the actual time it took to update the frame.
		Local ftime = GetTimer(2)
		ResetTimer(2)
		; Delta time in seconds between the two last frames.
		game.dt = game.updateRate / 1000 * ftime / game.updateRate
		game.loop(game.dt)   
	Forever
EndFunction


Function game:go()
	self.init()
	self.run()
EndFunction



; Simple game state manager
game.state = {}
game.state.states = {}
game.state.states[0] = {} ; Default state
game.state.states[0].update = Function (dt) game.update(dt) EndFunction
game.state.states[0].draw = Function () game.draw() EndFunction
game.state.state = 0
game.state.event$ = "init" ; Start in init event.
game.state.ids = {}


; Registers new game state by name.
Function game.state:register(statename, state)
	self.states[statename] = state
	InsertItem(self.ids, statename)   
EndFunction


; Remove game state from manager by state name.
Function game.state:remove(statename)
	self.states[statename] = {}
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		If self.ids[i] = statename Then RemoveItem(self.ids, i)
		i, v = NextItem(self.ids, i)
	Wend
EndFunction


; Clears all states from manager except default state.
Function game.state:clear()
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		self.states[v] = {}
		i, v = NextItem(self.ids, i)
	Wend
	       
	self.ids = {}

EndFunction


; Set active game state by name
Function game.state:set(statename)
	self.state = statename
	self.event$ = "init"
	If RawGet(self.states[self.state], "init") Then self.states[self.state].init()
EndFunction


; Get the name of a currently active game state
Function game.state:get()
	Return(self.state)
EndFunction


; Get the currently active event of a game state
Function game.state:getEvent()
	Return(self.event$)
EndFunction
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: Hollywood Blocks v. 0.1

Post by jalih »

Updated source and binary available here

Now uses "types" in primitives.hws for piece handling...

Graphics still need work, and I will rewrite the map and user interface drawing routines soon. Also, sounds would make a nice addition.
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: Hollywood Blocks v. 0.1

Post by jalih »

Here is a little modified Langton's Ant example. This one uses about five times less cpu than the previous version I posted and is quite a bit faster too...

Code: Select all

;
; Langton's Ant example
;

; Width = (#MAPX * #CELL_SIZE) + ((#MAPX + #PAD) * #PAD)
; Height = (#MAPY * #CELL_SIZE) + ((#MAPY + #PAD) * #PAD)
@DISPLAY {Width = 551, Height = 551, Title = "Langton's Ant"}


; Include game framework
@INCLUDE "game.hws"

; Include modules
@INCLUDE "primitives.hws"



; Some constants to help cleaning up code

Const #BUFFER = 1

Const #CELL_SIZE = 10

Const #PAD = 1

Const #MAPX = 50
Const #MAPY = 50

Const #EMPTY = 0
Const #TRAIL = 1

Dim map[#MAPY][#MAPX]


; Setup
Function game.load()
	game:setUpdateRate(60)
			
	dirs = { { x = -1, y = 0 }, { x = 0, y = 1 }, { x = 1, y = 0 }, { x = 0, y = -1 } } 
	antdir = 0
	cellr = rect:new( { Min = { x = 0, y = 0 }, Max = { x = #CELL_SIZE, y = #CELL_SIZE } } )
	antmap = point:new( { x = #MAPX / 2, y = #MAPY / 2 } )
	antbuf = point:new( { x = antmap.x * #CELL_SIZE, y = antmap.y * #CELL_SIZE } )
	antbuf = antbuf:add( { x = antmap.x * #PAD + #PAD, y = antmap.y * #PAD + #PAD } )
	colors = { #WHITE, #BLACK }
	
	lastm = point:new(antmap)
	lastc = #WHITE
	
	CreateBrush(#BUFFER, 551, 551)
	SelectBrush(#BUFFER)
	draw_map()
	EndSelect()
EndFunction


; Draw
Function game.draw()
	SelectBrush(1)
	Local scrpt = lastm:add({ x = lastm.x * #CELL_SIZE * #PAD + #PAD, y = lastm.y * #CELL_SIZE * #PAD + #PAD })
	draw_rect(cellr:addpt(scrpt), lastc)
	EndSelect() 
	DisplayBrush(1, 0, 0)	
	draw_rect(cellr:addpt(antbuf), #RED)
EndFunction


; Update
Function game.update(dt)
	If map[antmap.y][antmap.x] = #TRAIL
		map[antmap.y][antmap.x] = #EMPTY
		lastc = #WHITE
		antdir = (antdir + 1) % 4
	Else
		map[antmap.y][antmap.x] = #TRAIL
		lastc = #BLACK
		antdir = (antdir + 3) % 4
	EndIf
	
	lastm = point:new(antmap)		
	antmap = antmap:add(dirs[antdir])
	antmap.x = (antmap.x + #MAPX) % #MAPX
	antmap.y = (antmap.y + #MAPY) % #MAPY

	antbuf = antmap:add( { x = antmap.x * #CELL_SIZE * #PAD + #PAD, y = antmap.y * #CELL_SIZE * #PAD + #PAD } )

	
EndFunction

;------------------------------------------------------------------------------------------------------

Function draw_rect(rect, color)
	Box(rect.min.x, rect.min.y, rect:dx(), rect:dy(), color)
EndFunction


Function draw_map()
	SetFillStyle(#FILLCOLOR)
	For y = 0 To #MAPY - 1
		For x = 0 To #MAPX - 1
			Local p1 = point:new( { x = x * #CELL_SIZE, y = y * #CELL_SIZE } )
			Local p2 = { x = x * #PAD + #PAD, y = y * #PAD + #PAD }
			Local p = p1:add(p2)
			draw_rect(cellr:addpt(p), colors[map[y][x]])
		Next
	Next

EndFunction


; Go!
game:go()
You need the current primitives module:

Code: Select all

;
; Simple helper "classes"
;

; Point "class"
point = {}


; Metatable for point "class"
point.mt = {}

Function point.mt.__index(p, id)
	If id = "x" Or id = "y" Then Return(0)
EndFunction


; Methods for point "class"
Function point:new(point)
	Local p
	If point = Nil Then p = {} Else p = CopyTable(point)
	
	Function p:add(point)
		Local pp = CopyTable(self)
		pp.x = pp.x + point.x
		pp.y = pp.y + point.y
		Return(pp)
	EndFunction

	Function p:sub(point)
		Local pp = CopyTable(self)
		pp.x = pp.x - point.x
		pp.y = pp.y - point.y
		Return(pp)
	EndFunction

	Function p:mul(number)
		Local pp = CopyTable(self)
		pp.x = pp.x * number
		pp.y = pp.y * number
		Return(pp)
	EndFunction
	
	Function p:divide(number)
		Local pp = CopyTable(self)
		pp.x = pp.x / number
		pp.y = pp.y / number
		Return(pp)
	EndFunction	
	
	
	SetMetaTable(p, self.mt)
	
	Return(p)
	
EndFunction

 

; Rectangle "class"
rect = {}

; Metatable for rectangle "class"
rect.mt = {}

Function rect.mt.__index(r, id)
	If id = "min" Or id = "max" Then Return( { x = 0, y = 0 } )
EndFunction



; Methods for rectangle "class" 
Function rect:new(rectangle)
	Local r
	If rectangle = Nil Then r = {} Else r = CopyTable(rectangle)
	
	Function r:addpt(point)
		Local rr = CopyTable(self)
		rr.min.x = rr.min.x + point.x
		rr.min.y = rr.min.y + point.y
		rr.max.x = rr.max.x + point.x
		rr.max.y = rr.max.y + point.y
		Return(rr)
	EndFunction

	Function r:subpt(point)
		Local rr = CopyTable(self)
		rr.min.x = rr.min.x - point.x
		rr.min.y = rr.min.y - point.y
		rr.max.x = rr.max.x - point.x
		rr.max.y = rr.max.y - point.y
		Return(rr)
	EndFunction
	
	Function r:dx()
		Return(self.max.x - self.min.x)
	EndFunction
	
	Function r:dy()
		Return(self.max.y - self.min.y)
	EndFunction
	
	Function r:eq(rect)
		If self.min.x = rect.min.x And
		   self.min.y = rect.min.y And
		   self.max.x = rect.max.x And
		   self.max.y = rect.max.y
			Return(True)
		Else
			Return(False)
		EndIf

	EndFunction

	Function r:overlap(rect)
		If self.min.x > rect.min.x + (rect.max.x - rect.min.x) - 1 Or
		   self.min.y > rect.min.y + (rect.max.y - rect.min.y) - 1 Or
		   rect.min.x > self.min.x + self:dx() - 1 Or
		   rect.min.y > self.min.y + self:dy() - 1
			Return(False)
		Else
			Return(True)
		EndIf
	EndFunction
		
	
	SetMetaTable(r, self.mt)
	
	
	Return(r)
	
EndFunction
And you also need my game template:

Code: Select all

;
; Game template module: game.hws
;

game = {}
game.updateRate = 20 ; Default to 20 millisec = 50 fps.
game.dt = 0
game.fps = 0


; Game setup function for the user.
Function game.load()
	; Our game must override this dummy function.
EndFunction


; Game update function for the user
Function game.update(dt)
	; Our game must override this dummy function.
EndFunction


; Game draw frame function for the user
Function game.draw()
	; Our game must override this dummy function.   
EndFunction


; Our game framework init function. Calls just user game setup function for now
; and starts double buffering.
Function game.init()
	game.load()
	BeginDoubleBuffer()   
EndFunction


; Basic game loop function for our framework.
Function game.loop(dt)
	If game.state.event$ = "init" Or game.state.event$ = "update"
		game.state.event$ = "draw"
		If RawGet(game.state.states[game.state.state], "draw") Then game.state.states[game.state.state].draw()
		Flip()	
	EndIf

	If game.state.event$ = "draw"
		game.state.event$ = "update"
		If RawGet(game.state.states[game.state.state], "update") Then game.state.states[game.state.state].update(dt)	
	EndIf
EndFunction


; Set update rate for the game.
Function game:setUpdateRate(fps)
	self.updateRate = 1000 / fps
EndFunction


; Get the current fps-rate.
Function game:getFPS()
	Return(Round(1000 / (self.dt * 1000)))
EndFunction


; Runs the game, handles calling the game loop and timing.
Function game.run()
	StartTimer(1)
	StartTimer(2)

	Repeat
		; Try to lock the frame update rate to game.updateRate
		WaitTimer(1, game.updateRate)
		; Get the actual time it took to update the frame.
		Local ftime = GetTimer(2)
		ResetTimer(2)
		; Delta time in seconds between the two last frames.
		game.dt = game.updateRate / 1000 * ftime / game.updateRate
		game.loop(game.dt)   
	Forever
EndFunction


Function game:go()
	self.init()
	self.run()
EndFunction



; Simple game state manager
game.state = {}
game.state.states = {}
game.state.states[0] = {} ; Default state
game.state.states[0].update = Function (dt) game.update(dt) EndFunction
game.state.states[0].draw = Function () game.draw() EndFunction
game.state.state = 0
game.state.event$ = "init" ; Start in init event.
game.state.ids = {}


; Registers new game state by name.
Function game.state:register(statename, state)
	self.states[statename] = state
	InsertItem(self.ids, statename)   
EndFunction


; Remove game state from manager by state name.
Function game.state:remove(statename)
	self.states[statename] = {}
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		If self.ids[i] = statename Then RemoveItem(self.ids, i)
		i, v = NextItem(self.ids, i)
	Wend
EndFunction


; Clears all states from manager except default state.
Function game.state:clear()
	Local i, v = NextItem(self.ids)
	While GetType(i) <> #NIL
		self.states[v] = {}
		i, v = NextItem(self.ids, i)
	Wend
	       
	self.ids = {}

EndFunction


; Set active game state by name
Function game.state:set(statename)
	self.state = statename
	self.event$ = "init"
	If RawGet(self.states[self.state], "init") Then self.states[self.state].init()
EndFunction


; Get the name of a currently active game state
Function game.state:get()
	Return(self.state)
EndFunction


; Get the currently active event of a game state
Function game.state:getEvent()
	Return(self.event$)
EndFunction
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: Hollywood Blocks v. 0.1

Post by jalih »

More little updates...

I made a little addition to my game template. Now it's possible to control, how game template handles draw events. There are currently two modes: "automatic" and "manual".

Automatic mode draws everything and flips buffers on every update frame.

Manual mode needs a call to game:drawNow() method to draw the next frame (on game state change, game template still automatically draws the first frame). This way, you can keep a high resolution on timing and still only update screen when you need to. Should use a little less cpu...

I will try to put new packages available soon...
jalih
Posts: 281
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: Hollywood Blocks v. 0.1

Post by jalih »

Packages updated... I ended up naming the new methods in game template a bit differently but you can look from blocks_src.zip, how to use those...

Don't forget to check out the cpu load in Hollywood Blocks now!
Post Reply