Lemma Soft Forums

Supporting creators of visual novels and story-based games since 2003.


Visit our new games list, blog aggregator, IRC, and wiki.
Activation problem? Email pytom@bishoujo.us.
It is currently Fri Nov 21, 2014 9:33 pm

All times are UTC - 5 hours [ DST ]


Forum rules


Ask questions about one topic per thread, and use a descriptive subject. "NotImplemented error in script.rpy" is a good subject, "Tom's problems" is not. Remember to include all of traceback.txt or error.txt when reporting a problem, as well as the relevant lines of script. Use the [code] tag to format scripts.



Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Wed Nov 02, 2011 1:59 am 
Miko-Class Veteran
User avatar

Joined: Thu May 14, 2009 8:15 pm
Posts: 633
Projects: Castle of Arhannia
So I've been asked to do a tutorial on the drag and drop system. I'm not sure how well I can explain its uses, but apparently that's not a problem...

disclaimer: I'm an engineering dropout. My way of coding is not necessarily proper.

Of course, trying to explain things aimlessly would be silly, so I'm going to use an example mini-game and just explain how it works and how I thought it up. Now then,

How to make a jigsaw puzzle using drag and drop!!

Specifically, the following is how to make a puzzle that gives the player a disorganized mess of 12 pieces next to a 3 x 4 grid, then expects them to form a picture. The fully working code is attached, and uses an empty space image made in 10 sec on paint, as well as a random drawing I had lying around.

Also, out of personal preference, the example will use as little in python as possible, so that it integrates better with renpy. With only a little work, this example could have a cutscene that interrupts the puzzle to show an unrelated conversation, before going right back to the unchanged puzzle. Also, I have trouble mentally dealing with vbox, hbox, and grid.

First, let's look at what the documentation shows us how to do. The first example doesn't really help, as a puzzle mini-game of any sort doesn't really need draggable windows are anything like that. The second one, however, shows how to keep track of what the player has done with your draggable stuff. This is the important part.

So how does that example work? It hands the draggable objects (the detectives) a function (the part with def) to use to what to do when they're dragged around. That function then uses this code:
Code:
if not drop:
            return
to check if the draggable object was placed on one of the specified drops (the places for the detectives to go). If it wasn't then nothing happens. If it was, the function continues on to the next part and records the thing that was dragged, and where it was dragged to, using this part:
Code:
        store.detective = drags[0].drag_name
        store.city = drop.drag_name

        return True

here, "store." is used to connect to the actual renpy code. drags[0] is used to specify that only the one object was dragged (it's possible to move multiple, if they're set up to do so). "return True" tells the program that the player did something meaningful.

So how does this relate to the jigsaw puzzle? It's almost the same code I used. In fact, you could use this for most mini-games made with drag and drop, just with slight variation.

Back to the main point, there's one very big difference between the detectives example and a jigsaw puzzle. That is, a jigsaw puzzle requires the player be able to place pieces over and over again, possibly in the wrong spots. This means two things:
1. The program needs to keep track of the positions of the pieces and update regularly, and
2. The program needs to be able to tell when the pieces are in the correct positions

Luckily, both of those requirements can be solve in the same way. The program needs a list of positions corresponding to each piece. That way, each time the player places a piece, the game can check the piece positions to see if they're correct. In my code, I used piecelist which contains a list of two element list (x and y), so my code comes out like this:
Code:
drag_name "01 piece"
            child im.Crop("ShizukaClassroom_0001.jpg", 120,0, 120, 207)
                  #^this is an old function for cropping, since the crop warper
                  #didn't work
            droppable False
            dragged piece_dragged
            xpos piecelist[1][0] ypos piecelist[1][1]
                  #^the first number is which piece, the second is which coordinate

You might notice the strange name I chose. This is so that the first two characters can be parsed into an integer. You'll see why later.

Now then. The draggable piece are defined, but the puzzle still needs spaces to put the pieces. Rather than keeping track of current positions, the places merely need a set of coordinate. It would be fine to enter the coordinate in, but it will actually save typing to use lists for this as well. Thus my code for the spaces looks like this:
Code:
drag:
            drag_name "00"
            child "empty space.png"
            draggable False
            xpos coorlistx[0] ypos coorlisty[0]

Here again, you see my strange naming scheme. This time however, the reason is so that the coordinates within the grid of empy spaces are easy to use. Thus, the two characters are actually two separate cooridinates (x and y).

Now then, after copying and pasting a whole bunch, there are now 12 of each type. The next thing to address, is the needed python function. In my code above, I used piece_dragged. As I stated earlier, the python function can be almost the same as that of the documentation. The only difference is that the function used for the jigsaw puzzle needs to keep track of the pieces even if the player doesn't place them in the slots. This is easier to show than to explain so here it is:
Code:
def piece_dragged(drags, drop):
       
        if not drop:
            store.piecelist[(int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1]))][0] = drags[0].x
            store.piecelist[(int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1]))][1] = drags[0].y
            return
           
        store.movedpiece = int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1])
        store.movedplace = [int(drop.drag_name[0]), int(drop.drag_name[1])]
       
        return True

Ugh! this looks so complicated and messy. What's going on here is a bunch of parsing (changing the format of the values to fit a different type). In this case, the information provided is in strings, but the values needed are integers. This is what I would guess as the hardest for renpy users who don't code much, as without sufficient knowlege of python it looks like gibberish. Let's break it down.
Code:
store.piecelist[(int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1]))][0] = drags[0].x
store.piecelist[(int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1]))][1] = drags[0].y
these lines store the new coordinates for a piece that wasn't moved onto one of the slots. the function int() is used to change the characters in drag_name into digits. It takes the first digit in drag_name, multiplies it by ten, then adds it to the second digit, giving the number for the piece that was moved. This number then accesses the coordinate in piecelist that corresponds to the moved piece so that the new coordinates can be stored.

The other part,
Code:
store.movedpiece = int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1])
store.movedplace = [int(drop.drag_name[0]), int(drop.drag_name[1])]

does almost the same thing, except that it just does the parsing so that actual work can be done elsewhere.

Still there? If that explanation just now didn't makes sense, then just trust me that it works, and ask for help if you need to do something similar.

So the code now has most of it's functionality, but it still need to work with the pieces that are in the slots. Before getting into the code for this, another possibility needs to be considered: What should the game do if the player places two pieces in the same spot? For various reasons, I chose to swap the pieces. Thus, my code covers two situations:
1. when a piece in a slot is placed in another slot with a piece in it, and
2. when a piece that's outside of the slots is placed in a slot with a piece in it
Both of these require a bit of juggling with the value. For this reason, I made two arbitrarily named variables to hold the values momentarily. Here's what that looks like:
Code:
if ([coorlistx[movedplace[0]], coorlisty[movedplace[1]]] in piecelist):
        python:
            t1 = piecelist[movedpiece]
            t2 = piecelist.index([coorlistx[movedplace[0]], coorlisty[movedplace[1]]])
            piecelist[movedpiece] = [coorlistx[movedplace[0]],coorlisty[movedplace[1]]]
            piecelist[t2] = t1
    else:
        $ piecelist[movedpiece] = [coorlistx[movedplace[0]],coorlisty[movedplace[1]]]

the first line checks if a piece is already in the slot, by checking if any of the coordinates in piecelist match the slot in question. If so, the pieces are swapped using more ugly python code. t1 contains the previous coordinates for the piece that as dragged. t2 contains the number for the piece that's in the wanted slot.

Hooray! the puzzle is almost playable. This is meaningless, though, if it doesn't check the win condition. As stated earlier, this can be done by comparing the current piece positions to the correct ones. Once again the coordinate lists can be used to save on typing (because they can be copy-pasted).

There are only two things left to do:
1. put the whole thing in a loop
2. set up the variables so that renpy knows what they are

for 1., the following format is probably easiest:
Code:
label puzzle:
    call screen jigsaw
    # update stuff here
    # check win condition
    jump puzzle


for 2., all that is needed is to supply the starting values of everything, in the format that will be used like so
Code:
label start:
    scene black
    image whole = "ShizukaClassroom_0001.jpg"
    python:
        coorlistx = [10, 130, 250, 370]
        coorlisty = [10, 217, 424]
        piecelist = [[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]]
        for i in range(12):
            x = renpy.random.randint(0, 59) + 621
            y = renpy.random.randint(0, 480)
            piecelist[i] = [x,y]
        movedpiece = 0
        movedplace = [0, 0]
    jump puzzle

The reason piecelist has so many values, is because renpy will try to evaluate the screen early, and thus will get an out of bounds error otherwise (<--this was hell for me to figure out). The reason for the random integers and the for loop is so that the piece don't appear in any particular order.

Finally! it is finished! I hope this is helpful. Attached is the complete working code.

On a last note, if anyone's interested, I also have mostly working code for a strategic board game, that uses much more advanced code. It's a triangular version of Ataxx (aka. infection)


Attachments:
File comment: jigsaw puzzle
jigsaw.zip [215.18 KiB]
Downloaded 205 times
Top
 Profile Send private message  
 
PostPosted: Wed Nov 02, 2011 4:17 am 
Veteran
User avatar

Joined: Sun Sep 04, 2011 1:36 am
Posts: 277
Projects: A thingie without a title. With messy code.
Hooray!! :D Thank you so much for taking the time to type and post that awesome tutorial!!

Now we can all have puzzles and bonus mini-games etc.!! :D :D


Hey everybody, it may look complicated in the post, but do download the file and see for yourselves, it will be much more easy to understand when you see it in person/with the pieces being moved and all. :D And once you get used to it, Drag & Drop is extremely useful for so many different fun applications in the games!! :D

_________________
(I drew my avatar especially to express the scary feeling I get from the code as I type it... XD)


Top
 Profile Send private message  
 
PostPosted: Wed Nov 02, 2011 11:00 am 
Veteran
User avatar

Joined: Mon Nov 22, 2010 10:52 am
Posts: 365
Location: Germany
Projects: 1plus3
Oh. My. God.
You are soooooo amazing! x3
Not only does this work, but you also don't have the cut the puzzlepieces by hand!
(I wanted to use 200 pieces, but this took forever in photoshop, even with slices! D:
You're saving a lot of my time. <3)
You're a genius, I don't even know how to thank you!

_________________
ImageImage
Image


Top
 Profile Send private message  
 
PostPosted: Wed Nov 02, 2011 7:32 pm 
Miko-Class Veteran
User avatar

Joined: Thu May 14, 2009 8:15 pm
Posts: 633
Projects: Castle of Arhannia
@ryouko:
200 pieces?! you mean like a 10x20 grid?! How are you even going to fit that in the screen?

Anyway, maybe this code save you a bit more time and space: rather than typing out all those drags and such, you can also break into python and use a for loop. It's rather awkard though, so if it's mentally easier, you should stick with the previous method. here's about what that looks like:
Code:
python:
    BigPuzzle = DragGroup()
    for i in range(10):
        for j in range(20):
            BigPuzzle.add(d="emptyspace.jpg", drag_name=(str(i) + str(j)), draggable=False)
            BigPuzzle.get_child_by_name(str(i) + str(j)).snap(coorlistx[i], coorlisty[j])
    for i in range(10):
        for j in range(20):
            BigPuzzle.add(d=(im.Crop("pic.jpg", picwidth* i/10, picheight * j/20, picwidth/10, picheight/20), drag_name=(str(floor((j * 10 + i)/100)) + str(floor(((j*10 + i)%100)/10)) + str((j*10 +i)%100%10) + " piece"), droppable=False, dragged=piece_dragged)
            BigPuzzle.get_child_by_name(str(floor((j * 10 + i)/100)) + str(floor(((j*10 + i)%100)/10)) + str((j*10 +i)%100%10) + " piece").snap(piecelist[j*10 +i][0], piecelist[j*10 +i][1])


I haven't tested this with the jigsaw puzzle, but I did something similar in my other project. I know this method work, but the above code might have typos or errors. the str() function turns values into strings, and the percents signs are a special operation that I believe was called modulo division. It divides integers and returns the remainder instead of the quotient.


Top
 Profile Send private message  
 
PostPosted: Thu Nov 03, 2011 4:20 am 
Veteran
User avatar

Joined: Sun Sep 04, 2011 1:36 am
Posts: 277
Projects: A thingie without a title. With messy code.
Ohh, the additional code for the large size puzzles seems so awesomely promising too! :D
I tried to use it but I must be doing something wrong because I couldn't get it to work. ^^; I'll try again when I'm less sleepy, and hopefully I'll figure out how to use it then.

So for the time being I just used the normal version (...I typed 144 pieces ^^; ), although now I keep getting a weird error making the game crash and saying "IndexError: tuple index out of range". ^^;

Any idea what I'm doing wrong? ^^;;

_________________
(I drew my avatar especially to express the scary feeling I get from the code as I type it... XD)


Top
 Profile Send private message  
 
PostPosted: Thu Nov 03, 2011 11:40 am 
Veteran
User avatar

Joined: Mon Nov 22, 2010 10:52 am
Posts: 365
Location: Germany
Projects: 1plus3
Quote:
200 pieces?! you mean like a 10x20 grid?! How are you even going to fit that in the screen?

By making the pieces smaller? =o

Oh, the code looks really interesting. *_*
But idiot me has problems with throwing it in. D:"
Where exactly is it to put in?
The screen thingy, the label? =o
It's always complaining about this "is not terminated with a newline. (Check strings and parenthesis.)" thing in
Code:
BigPuzzle.add(d=(im.Crop("Puzzle/Nyv.png", 981* i/10, 491 * j/20, 981/10, 491/20), drag_name=(str(floor((j * 10 + i)/100)) + str(floor(((j*10 + i)%100)/10)) + str((j*10 +i)%100%10) + " piece"), droppable=False, dragged=piece_dragged)

[Errr... are you even supposed to insert the picheight in the code, or should I use just "pictheight" [not the real number]?]

Thank you for your help. <3

- Ryouko

_________________
ImageImage
Image


Top
 Profile Send private message  
 
PostPosted: Thu Nov 03, 2011 10:22 pm 
Miko-Class Veteran
User avatar

Joined: Thu May 14, 2009 8:15 pm
Posts: 633
Projects: Castle of Arhannia
@Soraminako:
in general, whenever you get "index out of range" it means your code tried to check more values than one of your lists/tuples actually has. It would be more helpful to know which one is giving you the error.

If it helps, the only time I got that was when I was giving the initial values. It was because I gave piecelist and the coorlists less values than were required to run the screens, then appended them.

@Ryouko:
ah, sorry. I wasn't sure how much you would already understand.

the code for the for loop version completely replaces the code for the screen. the dragged function will need to be adjusted to read three digits instead of two (meaning have the first character be multiplied by 100, second by 10, etc.), but otherwise that remains unchanged, as does the label. picheight and picwidth can both be used either way. If you use the variables instead of the actual height and width, you just need to define them before you call the label.

the error is my fault. I just counted the number of left and right parentheses, and they didn't match... *quickly pastes it into jedit Ah! Another right parenthesis is needed in the d= section. this should work:
Code:
BigPuzzle.add(d=(im.Crop("Puzzle/Nyv.png", 981* i/10, 491 * j/20, 981/10, 491/20)), drag_name=(str(floor((j * 10 + i)/100)) + str(floor(((j*10 + i)%100)/10)) + str((j*10 +i)%100%10) + " piece"), droppable=False, dragged=piece_dragged)


Top
 Profile Send private message  
 
PostPosted: Fri Nov 04, 2011 1:53 am 
Veteran
User avatar

Joined: Sun Sep 04, 2011 1:36 am
Posts: 277
Projects: A thingie without a title. With messy code.
Playing with code is always such a humbling hobby. :shock: At least for me, since it always ends up with me feeling like a moron and re-reading each line of the posts as if I was examining ancient Greek to try and understand anything. ^^;; (I have no background in anything programming, so even the terms for it sometime take me a while to figure out. :oops: )

Progress was made, since before I hadn't even realized that picwidth and picheight were not technical terms but something to be replaced by numbers. /fail

However, I'm getting another error, so I'm not sure where I messed up. ^^; That error is from when I try to use the loop. :?
Quote:
I'm sorry, but an uncaught exception occurred.

While running game code:
File "game/script.rpy", line 33, in script
call screen jigsaw
File "game/script.rpy", line 22, in python
BigPuzzle.get_child_by_name(str(i) + str(j)).snap(coorlistx[i], coorlisty[j])
TypeError: add() got an unexpected keyword argument 'd'


When I try to use the versions with the 144 pieces which I did the other day, I get a different error, that one I mentioned before. I tried checking the piecelist and the coorlists like you mentioned, but I don't see anything wrong with them. ^^; (Then again, it might be right in front of my face, and I'm just not seeing it. ^^; )

If it's not a bother and if you don't mind taking a quick look at it, I've attached my script file to the post... ^^; It would be very helpful if you could take a quick look and tell me if you spot anything horribly amiss in any of the places the "IndexError: tuple index out of range" error might be coming from.
The puzzle is an image that's 648 wide by 600 high, cut in 12 lines of 12 squares, all 54 x 50.


Also, in the loop version, what do you mean by having the first character be multiplied by 100, second by 10? ^^; Is that in the definition of the function at the top of script? And if yes... how? ^^;

Sorry for the usual epic noobish-ness of the questions. My brain is slowly adapting to understanding code, but it freezes and goes blue-screen-of-death when it comes in contact with more complicated bits... ^^;; (I must be a masochist for loving the Drag & Drop so much despite that, but well, it has such potential for awesome, and I figure if I persevere I'll eventually learn to use it and stop being such a noob. ^^; )


Attachments:
script.rpy [3.18 KiB]
Downloaded 26 times
script2.rpy [69.79 KiB]
Downloaded 41 times

_________________
(I drew my avatar especially to express the scary feeling I get from the code as I type it... XD)
Top
 Profile Send private message  
 
PostPosted: Fri Nov 04, 2011 1:07 pm 
Miko-Class Veteran
User avatar

Joined: Thu May 14, 2009 8:15 pm
Posts: 633
Projects: Castle of Arhannia
Quote:
add() got an unexpected keyword argument 'd'

Ugh,...I knew this would happen. See, this is what I meant in my earlier pm about my code not being accurate.

I completely forgot the part that all those parameters are supposed to be for the function Drag(), placed within the function add(). let me amend that again...
Code:
BigPuzzle.add(Drag(d=(im.Crop("Puzzle/Nyv.png", 981* i/10, 491 * j/20, 981/10, 491/20)), drag_name=(str(floor((j * 10 + i)/100)) + str(floor(((j*10 + i)%100)/10)) + str((j*10 +i)%100%10) + " piece"), droppable=False, dragged=piece_dragged))


my apologies. If it means anything, this is partially because I can't currently access both my laptop and the internet very often.

anyway. Soraminako, I looked through your code, and I've found 4 problems with script2:
1. the very end of the part beginning with
Code:
if piecelist == [[coorlistx[0], coorlisty[0]],
        etc...
needs another right bracket. it seems to be on line 2179.

2. your section for initially defining piecelist was about 20 short of how many slots are needed. I suggest you not put the whole list on one line as that's really hard to read. I counted them by adding a new line after every 5, and it made it much easier.

3. your pieces need unique drag_names. this is why I was saying earlier that the dragged function would need to compensate for a third digit. You have over a hundred that each need their own number.

4. adding letters at the end of slot names is an interesting idea, but it doesn't work here. You'll need to use two digits per coordinate if you're still going to go with the coordinate system I used.

Quote:
what do you mean by having the first character be multiplied by 100, second by 10?

okay, so take a look at the lines in the dragged function that parse the code. notice how they use things like drag_name[0] and drag_name[1]? these are taking the first and second character of the drag_name, so they can individually be used as digits (which is necessary to avoid using lots of if statements in the function). This means that to combine the two digits into one number, the digit in the ten's place must be multiplied by ten. If you have three digits, the digit in the hundred's place needs to be multiplied by 100. this also applies to the coordinates for the slots, if they get big enough to require multiple digits.


Top
 Profile Send private message  
 
PostPosted: Sat Nov 05, 2011 4:50 am 
Veteran
User avatar

Joined: Sun Sep 04, 2011 1:36 am
Posts: 277
Projects: A thingie without a title. With messy code.
Thank you so much! :D

And don't worry, I completely understand how that is, I often have to be jumping from a PC to another and it's terrible when you can't always access your files or go online. ^^ It's amazing you can help us so much despite the laptop/internet issue.

I'll try making those changes the next moment I get, but I had another question, does that mean that I should add Drag() also to the line above the one you mentioned? Since I see that one also has the same thing as the other one, without Drag()? Or is it not needed there?

Thank you so much for all your help!

_________________
(I drew my avatar especially to express the scary feeling I get from the code as I type it... XD)


Top
 Profile Send private message  
 
PostPosted: Mon Jul 02, 2012 2:02 am 
Newbie
User avatar

Joined: Mon Jul 02, 2012 12:49 am
Posts: 9
what if i want to do som,ething like this but with excess pieces? like just for an extra challenge? :D


Top
 Profile Send private message  
 
PostPosted: Mon Jul 02, 2012 2:04 am 
Newbie
User avatar

Joined: Mon Jul 02, 2012 12:49 am
Posts: 9
or what if i repeat some pieces so that it would be easier?


Top
 Profile Send private message  
 
PostPosted: Mon Jul 02, 2012 10:26 pm 
Miko-Class Veteran
User avatar

Joined: Thu May 14, 2009 8:15 pm
Posts: 633
Projects: Castle of Arhannia
I'm not sure about adding extra pieces to make it easier as I don't know if multiple drags are allowed to have the same name. I'm also not sure that would make it easier, as it could also make the puzzle more confusing. generally puzzles are easier when you have exactly the right number of pieces, as that gives the solver a clue to the solution.

as for adding fake pieces, you just add in drags with unrelated images in the child parameter and then don't include them in the solution.


Top
 Profile Send private message  
 
PostPosted: Sat Jun 22, 2013 11:26 am 
Eileen-Class Veteran
User avatar

Joined: Fri Mar 16, 2012 11:38 am
Posts: 1211
Location: Tokyo, Japan
Projects: Rika
IRC Nick: Trick
This code is really interesting, but when pieces are dragged near the bottom of the screen, they start freaking out and flashing all over the place.

Am I the only one who had this issue?

/forum necromancy

_________________
"We must teach them through the tools with which they are comfortable."

The #renpy IRC channel is a great place to chat with other devs. Due to the nature of IRC and timezone differences, people probably won't reply right away.

If you'd like to view or use any code from my VN PM me. All code is freely available without restriction, but also without warranty or (much) support.


Top
 Profile Send private message  
 
PostPosted: Sat Jun 22, 2013 2:32 pm 
Miko-Class Veteran
User avatar

Joined: Thu May 14, 2009 8:15 pm
Posts: 633
Projects: Castle of Arhannia
that's a problem that came up after renpy was updated a few times. I've noticed it too.

I'm not sure what causes the problem, but it didn't really affect my puzzle game Leave the Room much, so a possible solution is just making smaller jigsaw puzzles. the pic I used wasn't resized at all.

old code is old. nowadays I would suggest to anyone making a jigsaw puzzle to set up an overall point and click system first, just cause it's soooo much easier that way.


Top
 Profile Send private message  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next

All times are UTC - 5 hours [ DST ]


Who is online

Users browsing this forum: Bing [Bot], Yahoo [Bot]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Protected by Anti-Spam ACP
Powered by phpBB® Forum Software © phpBB Group