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...
Hollywood Blocks v. 0.1
Re: Hollywood Blocks v. 0.1
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:
Now it's easy to calculate from map coordinates to screen coordinates:
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
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
Re: Hollywood Blocks v. 0.1
My basic primitive helper "class" is starting to shape up...
Currently it includes only point and rectangle "types".
And here is a simple example demonstrating it's usage:
You also need my game template:
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
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()
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
Re: Hollywood Blocks v. 0.1
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.
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.
Re: Hollywood Blocks v. 0.1
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...
You need the current primitives module:
And you also need my game template:
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()
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
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
Re: Hollywood Blocks v. 0.1
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...
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...
Re: Hollywood Blocks v. 0.1
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!
Don't forget to check out the cpu load in Hollywood Blocks now!