Drag and Drop Tutorial-ish thing

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Message
Author
KimiYoriBaka
Miko-Class Veteran
Posts: 636
Joined: Thu May 14, 2009 8:15 pm
Projects: Castle of Arhannia
Contact:

Drag and Drop Tutorial-ish thing

#1 Post by KimiYoriBaka »

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: Select all

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: Select all

        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: Select all

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: Select all

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: Select all

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: Select all

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: Select all

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: Select all

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: Select all

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: Select all

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
jigsaw.zip
jigsaw puzzle
(215.18 KiB) Downloaded 1077 times

Soraminako
Veteran
Posts: 277
Joined: Sun Sep 04, 2011 1:36 am
Projects: A thingie without a title. With messy code.
Contact:

Re: Drag and Drop Tutorial-ish thing

#2 Post by Soraminako »

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)

Endorphin
Veteran
Posts: 366
Joined: Mon Nov 22, 2010 10:52 am
Location: Germany
Contact:

Re: Drag and Drop Tutorial-ish thing

#3 Post by Endorphin »

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!

KimiYoriBaka
Miko-Class Veteran
Posts: 636
Joined: Thu May 14, 2009 8:15 pm
Projects: Castle of Arhannia
Contact:

Re: Drag and Drop Tutorial-ish thing

#4 Post by KimiYoriBaka »

@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: Select all

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.

Soraminako
Veteran
Posts: 277
Joined: Sun Sep 04, 2011 1:36 am
Projects: A thingie without a title. With messy code.
Contact:

Re: Drag and Drop Tutorial-ish thing

#5 Post by Soraminako »

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)

Endorphin
Veteran
Posts: 366
Joined: Mon Nov 22, 2010 10:52 am
Location: Germany
Contact:

Re: Drag and Drop Tutorial-ish thing

#6 Post by Endorphin »

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: Select all

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

KimiYoriBaka
Miko-Class Veteran
Posts: 636
Joined: Thu May 14, 2009 8:15 pm
Projects: Castle of Arhannia
Contact:

Re: Drag and Drop Tutorial-ish thing

#7 Post by KimiYoriBaka »

@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: Select all

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)

Soraminako
Veteran
Posts: 277
Joined: Sun Sep 04, 2011 1:36 am
Projects: A thingie without a title. With messy code.
Contact:

Re: Drag and Drop Tutorial-ish thing

#8 Post by Soraminako »

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. :?
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, 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 112 times
script2.rpy
(69.79 KiB) Downloaded 139 times
(I drew my avatar especially to express the scary feeling I get from the code as I type it... XD)

KimiYoriBaka
Miko-Class Veteran
Posts: 636
Joined: Thu May 14, 2009 8:15 pm
Projects: Castle of Arhannia
Contact:

Re: Drag and Drop Tutorial-ish thing

#9 Post by KimiYoriBaka »

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: Select all

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: Select all

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.
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.

Soraminako
Veteran
Posts: 277
Joined: Sun Sep 04, 2011 1:36 am
Projects: A thingie without a title. With messy code.
Contact:

Re: Drag and Drop Tutorial-ish thing

#10 Post by Soraminako »

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)

User avatar
aicilef37
Newbie
Posts: 9
Joined: Mon Jul 02, 2012 12:49 am
Contact:

Re: Drag and Drop Tutorial-ish thing

#11 Post by aicilef37 »

what if i want to do som,ething like this but with excess pieces? like just for an extra challenge? :D

User avatar
aicilef37
Newbie
Posts: 9
Joined: Mon Jul 02, 2012 12:49 am
Contact:

Re: Drag and Drop Tutorial-ish thing

#12 Post by aicilef37 »

or what if i repeat some pieces so that it would be easier?

KimiYoriBaka
Miko-Class Veteran
Posts: 636
Joined: Thu May 14, 2009 8:15 pm
Projects: Castle of Arhannia
Contact:

Re: Drag and Drop Tutorial-ish thing

#13 Post by KimiYoriBaka »

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.

TrickWithAKnife
Eileen-Class Veteran
Posts: 1261
Joined: Fri Mar 16, 2012 11:38 am
Projects: Rika
Organization: Solo (for now)
IRC Nick: Trick
Location: Tokyo, Japan
Contact:

Re: Drag and Drop Tutorial-ish thing

#14 Post by TrickWithAKnife »

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.

KimiYoriBaka
Miko-Class Veteran
Posts: 636
Joined: Thu May 14, 2009 8:15 pm
Projects: Castle of Arhannia
Contact:

Re: Drag and Drop Tutorial-ish thing

#15 Post by KimiYoriBaka »

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.

Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], DragoonHP, Kocker, Ocelot