This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Bomb Bloke's profile picture

BBTetris

Started by Bomb Bloke, 09 November 2013 - 12:24 AM
Bomb Bloke #1
Posted 09 November 2013 - 01:24 AM
So I thought I'd try my hand at something for a treasure disk - Yes, it's Tetris. I've probably overdone it, but so be it.

Screenshot
http://www.youtube.com/watch?v=9dHwvWWfV-0

Ever seen those little grey hand-held Tetris games, with lots of variants built in? This is based on those. It's relatively simple (there's only a handful of extra game modes, and they're all variants of, well, Tetris), but you can mix and match to whatever suits your fancy.

When I first wrote this post I marked it as beta code, but I'm now satisfied it's stable and ready to put down as a "final release". By whatever coincedence wojbie posted his mirror util at about the same time as this, which was fortunate as it allowed me to quickly pick up some holes in my monitor-handling logic (since fixed).

I guess I may be able to claim the "first ComputerCraft game with music" title here - if one or more Iron Note Blocks from MoarPeripherals are available, along with the "note" resource pack downloadable from the same link, the game will play background music!

So, without further ado:

pastebin get nKQZwgtv bbtetris

Version History2013/11/09
Beta 1.0
Initial release.

2013/11/10
Beta 1.1.0
Terminate command now triggers the program's exit routine.
An INI file is now generated on first launch ("BBTetris.ini").

Beta 1.1.1
No longer responds to resizes of monitors it doesn't control…

Beta 1.1.2
… nor touches on monitors it doesn't control, nor does it try to restore from such displays on exit.

2013/11/16
Beta 1.1.3
Condensed code somewhat.

2013/11/18
Beta 1.1.4
Trimmed things down a fraction further.
"Trick" blocks chopped down to my "final" intended spawn rate (one in thirty).

2013/11/21
Version 1.0.0
No longer marked as beta.
"Trick" blocks are now one in twenty.
Fixed issues when trying to run from a read-only environment.
Seems later versions of CC use a case-sensitive file system, so switched all file references to lower-case for convenience.

2013/11/24
Version 1.0.1
No longer offers to save the game while running off a read-only disk (this did "work", but the saves were discarded when quitting).

2014/03/31
Version 1.0.2
ComputerCraft 1.6 compatibility (due to term.restore() changes).

2014/04/15
Version 1.0.3
More ComputerCraft 1.6 compatibility (due to textutils.serialize() changes).

2014/07/07
Version 1.0.4
MoarPeripherals Iron Note Block support.

2015/04/06
Version 1.0.5
Updated to newer MoarPeripheral's directory layouts.
CC 1.74's "mouse_up" and "key_up" events will not halt the intro animation.

2015/05/17
Version 1.0.6
Apparently setTextScale now triggers a monitor_resize event a tick or two later. Added a handler for that.
Added Pocket Computer support.
Edited on 10 February 2016 - 10:16 PM
Zudo #2
Posted 09 November 2013 - 05:40 AM
I will try it ASAP. The screenies look good!
Yevano #3
Posted 09 November 2013 - 11:10 AM
Just played it. Great job on this. +1 for the save feature.
Wojbie #4
Posted 09 November 2013 - 12:01 PM
I Love this program. Took it for a spin and even broke it! :D/>
If you run program on advanced screen and do a crtl+t screen stays on redirected on monitor. You could do that pullEventRaw() trick and make terminate quit program normally (witch includes term.restore from what i can see and would even save high-score! :P/> )
Also i would love option to give it in argument side of computer where monitor i want to use it is or none for it to skip that first y/n question. Example "tetris right" or "tetris none". That would be great for arcade room i have in my base.

Also love that you included modes and a awesome loading screen.
Edited on 09 November 2013 - 11:08 AM
Bomb Bloke #5
Posted 09 November 2013 - 07:26 PM
I'd discarded the idea of using raw events out of hand… I usually see it used to make programs "unquitable". Decided that if someone wanted to try forcing the program to "drop everything" then it'd do exactly that, and they could live with the consequences. ;)/>

That said, that was before the monitor code went in, and I can see that the average user is going to be scratching their head trying to work out how to fix that one, so "terminate" event handling is now in there. (Was tempted to have them prevent saving scores etc, but decided to have that go ahead anyway.)

Having to type a parameter to indicate you don't want to use a monitor is more effort then just tapping "n" when the program launches. Still, I suppose that's enough of an excuse to implement an INI file - now it'll generate one the first time you run it (BBTetris.ini), and if you open that up with your editor of choice the rest should be self explanatory.

Thanks for the feedback, folks! :)/>
Wojbie #6
Posted 09 November 2013 - 07:51 PM
Having to type a parameter to indicate you don't want to use a monitor is more effort then just tapping "n" when the program launches. Still, I suppose that's enough of an excuse to implement an INI file - now it'll generate one the first time you run it (BBTetris.ini), and if you open that up with your editor of choice the rest should be self explanatory.

Thanks for the feedback, folks! :)/>

Great so i will be able to specify in that .ini file that its supposed to play on "monitor_126" instead of first one it sees? Great!

That will work great for my needs too. Also i love how program dynamically handles monitor resizes . Nice piece of code. Its defiantly getting spot in my arcade
Bomb Bloke #7
Posted 09 November 2013 - 08:40 PM
Unfortunately it tried to handle resizes that weren't happening to the monitor it had control over (as I found on testing your mirroring tool), so I've again tweaked the code a bit.
Edited on 09 November 2013 - 07:40 PM
Wojbie #8
Posted 09 November 2013 - 08:51 PM
Unfortunately it tried to handle resizes that weren't happening to the monitor it had control over (as I found on testing your mirroring tool), so I've again tweaked the code a bit.

That was making me run ragged!!! I was looking at debug event queue on another monitor and I was like Wat?
Hehehehehe.
Symmetryc #9
Posted 10 November 2013 - 08:48 AM
There are a few things that you could've done to make your life easier, but very nice and solid program nonetheless :D/>
Xenthera #10
Posted 10 November 2013 - 06:03 PM
Not gonna lie. I spent a few hours playing it…
Bomb Bloke #11
Posted 16 November 2013 - 02:14 AM
There are a few things that you could've done to make your life easier, but very nice and solid program nonetheless :D/>
I'm about at the point where I'm pretty sure this thing is "stable", but I'd still appreciate any tips on improving it (even if I might not use them until I write some other program). There's still a lot about Lua that I don't know. :)/>

Not gonna lie. I spent a few hours playing it…
The save function went in directly after the basic game engine was done, specifically to stop me playing each and every trial run to completion.

At one stage I realised that I was gaming as a character who was getting addicted to a game, and that broke me out of it hard and fast. ;)/>
Symmetryc #12
Posted 16 November 2013 - 12:38 PM
There are a few things that you could've done to make your life easier, but very nice and solid program nonetheless :D/>
I'm about at the point where I'm pretty sure this thing is "stable", but I'd still appreciate any tips on improving it (even if I might not use them until I write some other program). There's still a lot about Lua that I don't know. :)/>
They aren't really improvements, but they're just more convenient. For example, you can define multiline strings like this:

[[
Hello!
Bye!
]]
You can use these to print out things that would usually take more than one print statement or would be convoluted if done in one print statement, ex:

-- old way
print("\nIf you wish to copy me to your internal drive (for play without the disk), type:\n\ncp \\disk\\BBTetris \\BBTetris\n")

-- new way
print([[
If you wish to copy me to your internal drive (for play without the disk), type:

cp \disk\BBTetris \BBTetris
]])

-- Note: Putting "\\" in multiline strings will simply give you "\\"; Characters won't be escaped.
Also, is there any reason that you're using term.write over write? I believe write has more features (such as \n support), although I'm not entirely sure.

Furthermore, are you familiar with the concept of objects? You might want to check out a tutorial on OOP in the Tutorials section, because from the way it looks, you could probably halve the amount of lines if you were to use objects over huge if statements :P/>.

However, this is still a very robust and impressive program and keep up the great work :)/>! (Hope this helps :)/>)
Edited on 16 November 2013 - 04:24 PM
Bomb Bloke #13
Posted 16 November 2013 - 06:39 PM
I started out with just the intention of having monitor support, and thought "term.write" would somehow make things easier there. As I found out there wasn't much difference, but there are a few spots where it's specifically needed (eg, where I write stuff off-screen). I suppose since it doesn't even consider word-wrapping and what-not it's also going to be faster then "write".

I'm familiar with Java and the joys of OOP, but I don't see how I'd implement any objects here. I mean, I could certainly throw at least one in, but I can't see how it'd be done without making the program longer… Sure, it'd let me shuffle code around, but that code wouldn't go away altogether: it'd just be wrapped up within additional function declarations elsewhere… :wacko:/> Dunno.

I'm also a little suspicious as to how OOP works in Lua: I mean, there are times when that technique certainly seems to fit (eg, you've got a ton of enemies on screen which all have the same properties/functions), but I rather suspect that Lua's implementation wastes bucket loads of RAM duplicating functions to pull it off (er, or at least, more RAM then it usually does ;)/> ). No idea, I'll have to try defining some and find out what the options are, as it can improve readability if nothing else.

I'd seen the [[]] thing used once before, but wasn't aware of its full capabilities. That'll indeed come in handy. :)/>

Thanks! :)/>
Yevano #14
Posted 16 November 2013 - 06:57 PM
but I rather suspect that Lua's implementation wastes bucket loads of RAM duplicating functions to pull it off

If you use metatables, you don't have to worry about that. The common thing to do is to create a class table to hold all your functions and set the metatable of all instances to that of the class. That way, they just call the class function, passing the object in as self.
Edited on 16 November 2013 - 05:57 PM
Bomb Bloke #15
Posted 16 November 2013 - 07:09 PM
Ah, that puts my mind to rest, thanks. :)/>
jay5476 #16
Posted 17 November 2013 - 04:47 AM
but I rather suspect that Lua's implementation wastes bucket loads of RAM duplicating functions to pull it off

If you use metatables, you don't have to worry about that. The common thing to do is to create a class table to hold all your functions and set the metatable of all instances to that of the class. That way, they just call the class function, passing the object in as self.
yes by using objects you can easily check collisions and data about that specific object(block)

EDIT: if i get time i will try to make my own version essentialy using OOP
Edited on 17 November 2013 - 03:47 AM
theoriginalbit #17
Posted 17 November 2013 - 07:52 AM
Nice work. I am curious as to why you're doing an else block with do block inside of it at 1213. Also don't forget about the error level of 0.

error("No file or line numbers shown, just this message in red", 0)
Bomb Bloke #18
Posted 17 November 2013 - 05:58 PM
I am curious as to why you're doing an else block with do block inside of it at 1213.
Just a quick way to have the "sides" table discarded when I was done with it.

Also don't forget about the error level of 0.

error("No file or line numbers shown, just this message in red", 0)
I think I'm not so much forgetting it as not knowing why I need it in the first place. :unsure:/>/>

If you're wondering about my use of the "erroring" value, that's less to report that "there was a problem at line such-and-such" and more to report things like "the user bulldozed my monitor!" - I then want it to go on to output the usual closing statements too. But I suspect that's not what you're getting at.
theoriginalbit #19
Posted 17 November 2013 - 06:45 PM
Just a quick way to have the "sides" table discarded when I was done with it.
But its local to the else block, so it will be discarded when the else block is finished.

I think I'm not so much forgetting it as not knowing why I need it in the first place. :unsure:/>/>

If you're wondering about my use of the "erroring" value, that's less to report that "there was a problem at line such-and-such" and more to report things like "the user bulldozed my monitor!" - I then want it to go on to output the usual closing statements too. But I suspect that's not what you're getting at.
Yeh I'm more talking about lines 1246-1251
Symmetryc #20
Posted 17 November 2013 - 08:23 PM
I'm also a little suspicious as to how OOP works in Lua: I mean, there are times when that technique certainly seems to fit (eg, you've got a ton of enemies on screen which all have the same properties/functions), but I rather suspect that Lua's implementation wastes bucket loads of RAM duplicating functions to pull it off (er, or at least, more RAM then it usually does ;)/> ). No idea, I'll have to try defining some and find out what the options are, as it can improve readability if nothing else.

Thanks! :)/>
One way to do OOP (or something resembling it) would be to do something like this (your implementation of blocks is probably way different, but you can probably get the idea of what I'm talking about):
Spoiler

local max_x, max_y = term.getSize()
local blocks = {
	types = {
		{
			{0, 1, 0};
			{1, 1, 0};
			{0, 1, 0};
		};
		{
			{1, 0, 0};
			{1, 0, 0};
			{1, 1, 0};
		}
			-- etc.
	};
	new = function(self, _x, _y)
		return {
			x = _x or math.random(1, max_x - 2);
			y = _y or math.random(1, max_y - 2);
			type = self.blocks[math.random(1, #self.blocks)];
		}
	end;

	--update method (moves blocks by 1)

	--collision check method

	--etc.
}
Edit: When I said it could be shortened, I meant that if you had an table full of screen objects or similar, you could just iterate through them when the user clicked and check to see if the click came in contact with any of them and if it did, run the function within that screen object's table rather than having to build a huge if statement with each screen object having its own part.

Ex:
Spoiler

local tScreenObjects = {}
local exit = function()
  error("Thank you for playing this game =D", 0)
end
-- use of button "class" (OOP), making it easy to create buttons and use of table
-- argument to enhance readability.
tScreenObjects.exitButton = button.new({x = 1, x2 = 5, y = 6, y2 = 7, func = exit, color = colors.red})

local event, x, y = os.pullEvent()
if event == "mouse_click" then
  -- Again, use of button "class"
  button.getButtonAt(tScreenObjects, x, y).func()
end
Edited on 17 November 2013 - 08:02 PM
Bomb Bloke #21
Posted 18 November 2013 - 07:09 AM
But its local to the else block, so it will be discarded when the else block is finished.
I got most of the way to work before realising that's what your answer would be… not 'cause I remembered that's how it worked so much as I figured out that you wouldn't ask if it didn't.

I have a memory of testing that out, but not a whit of memory as to what the result was. I must've shuffled that code in there later on thinking I knew what I was doing.

Yeh I'm more talking about lines 1246-1251
… and while at work I probably spent too much of my time refreshing Pastebin so I could review those lines (it was "under heavy load" most of the day, apparently).

Anyway, yeah, that's a good spot for it. :)/>

One way to do OOP (or something resembling it) would be to do something like this (your implementation of blocks is probably way different, but you can probably get the idea of what I'm talking about):
That's actually near identical, yes - a table of the things with a few variables to keep track of which is in use, and where it is.

I do get how OOP would be implemented here (my worry re function duplication wouldn't've applied even if true, given that I only have one block on screen at a time), what I don't get is the "why" (… given that I only have one block on screen at a time). But thanks.

I suspect it boils down to what one's familiar with, or one's personal style. For example, I find the system of applying functions to button objects a right nuisance to read, because although it's very clear that there's a selection of buttons and the program is checking to see if any have been clicked, it becomes a royal nuisance to work out what they do - I'd rather have all that code in the one place then spread out at intervals throughout the source. It's like the difference between reading a regular book or a "choose your own adventure" novel to me, and when a function is only called from one specific place in the code then the whole thing seems like a waste of time.

Dunno.
theoriginalbit #22
Posted 18 November 2013 - 07:18 AM
I got most of the way to work before realising that's what your answer would be… not 'cause I remembered that's how it worked so much as I figured out that you wouldn't ask if it didn't.

I have a memory of testing that out, but not a whit of memory as to what the result was. I must've shuffled that code in there later on thinking I knew what I was doing.
Haha, well you definitely don't need it ;P

… and while at work I probably spent too much of my time refreshing Pastebin so I could review those lines (it was "under heavy load" most of the day, apparently).

Anyway, yeah, that's a good spot for it. :)/>
of course it would be xP
Symmetryc #23
Posted 18 November 2013 - 02:21 PM
One way to do OOP (or something resembling it) would be to do something like this (your implementation of blocks is probably way different, but you can probably get the idea of what I'm talking about):
That's actually near identical, yes - a table of the things with a few variables to keep track of which is in use, and where it is.

I do get how OOP would be implemented here (my worry re function duplication wouldn't've applied even if true, given that I only have one block on screen at a time), what I don't get is the "why" (… given that I only have one block on screen at a time). But thanks.

I suspect it boils down to what one's familiar with, or one's personal style. For example, I find the system of applying functions to button objects a right nuisance to read, because although it's very clear that there's a selection of buttons and the program is checking to see if any have been clicked, it becomes a royal nuisance to work out what they do - I'd rather have all that code in the one place then spread out at intervals throughout the source. It's like the difference between reading a regular book or a "choose your own adventure" novel to me, and when a function is only called from one specific place in the code then the whole thing seems like a waste of time.

Dunno.
Each to their own I guess :)/>
mineo72 #24
Posted 14 December 2013 - 12:56 PM
This is Cool
Bomb Bloke #25
Posted 14 December 2013 - 09:52 PM
Thank you. :)/>
Agoldfish #26
Posted 18 January 2014 - 02:19 AM
I really hope Dan adds this in the treasure disk library. I love it! +1 for you, sire.

EDIT: 100TH POST! WOO!
Edited on 18 January 2014 - 03:19 PM
Bomb Bloke #27
Posted 07 July 2014 - 02:39 PM
Eeheeheeheehee.

http://www.youtube.com/watch?v=9dHwvWWfV-0