Tumgik
#sizecoding
andymakesgames · 5 months
Text
Balatro-Inspired Spinning Card Tweetcart Breakdown
I recently made a tweetcart of a spinning playing card inspired by finally playing Balatro, the poker roguelike everybody is talking about.
If you don't know what a tweetcart is, it's a type of size-coding where people write programs for the Pico-8 fantasy console where the source code is 280 characters of less, the length of a tweet.
I'm actually not on twitter any more, but I still like 280 characters as a limitation. I posted it on my mastodon and my tumblr.
Here's the tweetcart I'm writing about today:
Tumblr media
And here is the full 279 byte source code for this animation:
a=abs::_::cls()e=t()for r=0,46do for p=0,1,.025do j=sin(e)*20k=cos(e)*5f=1-p h=a(17-p*34)v=a(23-r)c=1+min(23-v,17-h)%5/3\1*6u=(r-1)/80z=a(p-.2)if(e%1<.5)c=a(r-5)<5and z<u+.03and(r==5or z>u)and 8or 8-sgn(h+v-9)/2 g=r+39pset((64+j)*p+(64-j)*f,(g+k)*p+(g-k)*f,c)end end flip()goto _
This post is available with much nicer formatting on the EMMA blog. You can read it here.
You can copy/paste that code into a blank Pico-8 file to try it yourself. I wrote it on Pico-8 version 0.2.6b.
I'm very pleased with this cart! From a strictly technical perspective I think it's my favorite that I've ever made. There is quite a bit going on to make the fake 3D as well as the design on the front and back of the card. In this post I'll be making the source code more readable as well as explaining some tools that are useful if you are making your own tweetcarts or just want some tricks for game dev and algorithmic art.
Expanding the Code
Tweetcarts tend to look completely impenetrable, but they are often less complex than they seem. The first thing to do when breaking down a tweetcart (which I highly recommend doing!) is to just add carriage returns after each command.
Removing these line breaks is a classic tweetcart method to save characters. Lua, the language used in Pico-8, often does not need a new line if a command does not end in a letter, so we can just remove them. Great for saving space, bad for readability. Here's that same code with some line breaks, spaces and indentation added:
a=abs ::_:: cls() e=t() for r=0,46 do for p=0,1,.025 do j=sin(e)*20 k=cos(e)*5 f=1-p h=a(17-p*34) v=a(23-r) c=1+min(23-v,17-h)%5/3\1*6 u=(r-1)/80 z=a(p-.2) if(e%1<.5) c= a(r-5) < 5 and z < u+.03 and (r==5 or z>u) and 8 or 8-sgn(h+v-9)/2 g=r+39 pset((64+j)*p+(64-j)*f,(g+k)*p+(g-k)*f,c) end end flip()goto _
Note: the card is 40 pixels wide and 46 pixels tall. Those number will come up a lot. As will 20 (half of 40) and 23 (half of 46).
Full Code with Variables and Comments
Finally, before I get into what each section is doing, here is an annotated version of the same code. In this code, variables have real names and I added comments:
[editor's note. this one came out terribly on tumblr. Please read the post on my other blog to see it]
This may be all you need to get a sense of how I made this animation, but the rest of this post will be looking at how each section of the code contributes to the final effect. Part of why I wanted to write this post is because I was happy with how many different tools I managed to use in such a small space.
flip() goto_
This pattern shows up in nearly every tweetcart:
::_:: MOST OF THE CODE flip()goto _
This has been written about in Pixienop's Tweetcart Basics which I highly recommend for anybody curious about the medium! The quick version is that using goto is shorter than declaring the full draw function that Pico-8 carts usually use.
Two Spinning Points
The card is drawn in rows starting from the top and going to the bottom. Each of these lines is defined by two points that move around a center point in an elliptical orbit.
The center of the top of the card is x=64 (dead center) and y=39 (a sort of arbitrary number that looked nice).
Then I get the distance away from that center that my two points will be using trigonometry.
x_dist = sin(time)*20 y_dist = cos(time)*5
Here are those points:
Tumblr media
P1 adds x_dist and y_dist to the center point and P2 subtracts those same values.
Those are just the points for the very top row. The outer for loop is the vertical rows. The center x position will be the same each time, but the y position increases with each row like this: y_pos = row+39
Here's how it looks when I draw every 3rd row going down:
Tumblr media
It is worth noting that Pico-8 handles sin() and cos() differently than most languages. Usually the input values for these functions are in radians (0 to two pi), but in Pico-8 it goes from 0 to 1. More info on that here. It takes a little getting used to but it is actually very handy. More info in a minute on why I like values between 0 and 1.
Time
In the shorter code, e is my time variable. I tend to use e for this. In my mind it stands for "elapsed time". In Pico-8 time() returns the current elapsed time in seconds. However, there is a shorter version, t(), which obviously is better for tweetcarts. But because I use the time value a lot, even the 3 characters for t() is often too much, so I store it in the single-letter variable e.
Because it is being used in sine and cosine for this tweetcart, every time e reaches 1, we've reached the end of a cycle. I would have liked to use t()/2 to slow this cart down to be a 2 second animation, but after a lot of fiddling I wound up being one character short. So it goes.
e is used in several places in the code, both to control the angle of the points and to determine which side of the card is facing the camera.
Here you can see how the sine value of e controls the rotation and how we go from showing the front of the card to showing the back when e%1 crosses the threshold of 0.5.
Tumblr media
Drawing and Distorting the Lines
Near the top and bottom of the loop we'll find the code that determines the shape of the card and draws the horizontal lines that make up the card. Here is the loop for drawing a single individual line using the code with expanded variable names:
for prc = 0,1,.025 do x_dist = sin(time)*20 y_dist = cos(time)*5 ... y_pos = row+39 pset( (64+x_dist)*prc + (64-x_dist)*(1-prc), (y_pos+y_dist)*prc + (y_pos-y_dist)*(1-prc), color) end
You might notice that I don't use Pico-8's line function! That's because each line is drawn pixel by pixel.
This tweetcart simulates a 3D object by treating each vertical row of the card as a line of pixels. I generate the points on either side of the card(p1 and p2 in this gif), and then interpolate between those two points. That's why the inner for loop creates a percentage from 0 to 1 instead of pixel positions. The entire card is drawn as individual pixels. I draw them in a line, but the color may change with each one, so they each get their own pset() call.
Here's a gif where I slow down this process to give you a peek at how these lines are being drawn every frame. For each row, I draw many pixels moving across the card between the two endpoints in the row.
Tumblr media
Here's the loop condition again: for prc = 0,1,.025 do
A step of 0.025 means there are 40 steps (0.025 * 40 = 1.0). That's the exact width of the card! When the card is completely facing the camera head-on, I will need 40 steps to make it across without leaving a gap in the pixels. When the card is skinnier, I'm still drawing all 40 pixels, but many of them will be in the same place. That's fine. The most recently drawn one will take priority.
Getting the actual X and Y position
I said that the position of each pixel is interpolated between the two points, but this line of code may be confusing:
y_pos = row+39 pset( (64+x_dist)*prc + (64-x_dist)*(1-prc), (y_pos+y_dist)*prc + (y_pos-y_dist)*(1-prc), color)
So let's unpack it a little bit. If you've ever used a Lerp() function in something like Unity you've used this sort of math. The idea is that we get two values (P1 and P2 in the above example), and we move between them such that a value of 0.0 gives us P1 and 1.0 gives us P2.
Here's a full cart that breaks down exactly what this math is doing:
Tumblr media
::_:: cls() time = t()/8 for row = 0,46 do for prc = 0,1,.025 do x_dist = sin(time)*20 y_dist = cos(time)*5 color = 9 + row % 3 p1x = 64 + x_dist p1y = row+39 + y_dist p2x = 64 - x_dist p2y = row+39 - y_dist x = p2x*prc + p1x*(1-prc) y = p2y*prc + p1y*(1-prc) pset( x, y, color) end end flip()goto _
I'm defining P1 and P2 very explicitly (getting an x and y for both), then I get the actual x and y position that I use by multiplying P2 by prc and P1 by (1-prc) and adding the results together.
This is easiest to understand when prc is 0.5, because then we're just taking an average. In school we learn that to average a set of numbers you add them up and then divide by how many you had. We can think of that as (p1+p2) / 2. This is the same as saying p1*0.5 + p2*0.5.
But the second way of writing it lets us take a weighted average if we want. We could say p1*0.75 + p2*0.25. Now the resulting value will be 75% of p1 and 25% of p2. If you laid the two values out on a number line, the result would be just 25% of the way to p2. As long as the two values being multiplied add up to exactly 1.0 you will get a weighted average between P1 and P2.
I can count on prc being a value between 0 and 1, so the inverse is 1.0 - prc. If prc is 0.8 then 1.0-prc is 0.2. Together they add up to 1!
I use this math everywhere in my work. It's a really easy way to move smoothly between values that might otherwise be tricky to work with.
Compressing
I'm using a little over 400 characters in the above example. But in the real cart, the relevant code inside the loops is this:
j=sin(e)*20 k=cos(e)*5 g=r+39 pset((64+j)*p+(64-j)*f,(g+k)*p+(g-k)*f,c)
which can be further condensed by removing the line breaks:
j=sin(e)*20k=cos(e)*5g=r+39pset((64+j)*p+(64-j)*f,(g+k)*p+(g-k)*f,c)
Because P1, P2 and the resulting interpolated positions x and y are never used again, there is no reason to waste chars by storing them in variables. So all of the interpolation is done in the call to pset().
There are a few parts of the calculation that are used more than once and are four characters or more. Those are stored as variables (j, k & g in this code). These variables tend to have the least helpful names because I usually do them right at the end to save a few chars so they wind up with whatever letters I have not used elsewhere.
Spinning & Drawing
Here's that same example, but with a checker pattern and the card spinning. (Keep in mind, in the real tweetcart the card is fully draw every frame and would not spin mid-draw)
Tumblr media
This technique allows me to distort the lines because I can specify two points and draw my lines between them. Great for fake 3D! Kind of annoying for actually drawing shapes, because now instead of using the normal Pico-8 drawing tools, I have to calculate the color I want based on the row (a whole number between0 and 46) and the x-prc (a float between 0 and 1).
Drawing the Back
Here's the code that handles drawing the back of the card:
h=a(17-p*34) v=a(23-r) c=1+min(23-v,17-h)%5/3\1*6
This is inside the nested for loops, so r is the row and p is a percentage of the way across the horizontal line.
c is the color that we will eventually draw in pset().
h and v are the approximate distance from the center of the card. a was previously assigned as a shorthand for abs() so you can think of those lines like this:
h=abs(17-p*34) v=abs(23-r)
v is the vertical distance. The card is 46 pixels tall so taking the absolute value of 23-r will give us the distance from the vertical center of the card. (ex: if r is 25, abs(23-r) = 2. and if r is 21, abs(23-r) still equals 2 )
As you can probably guess, h is the horizontal distance from the center. The card is 40 pixels wide, but I opted to shrink it a bit by multiplying p by 34 and subtracting that from half of 34 (17). The cardback just looks better with these lower values, and the diamond looks fine.
The next line, where I define c, is where things get confusing. It's a long line doing some clunky math. The critical thing is that when this line is done, I need c to equal 1 (dark blue) or 7 (white) on the Pico-8 color pallette.
Here's the whole thing: c=1+min(23-v,17-h)%5/3\1*6
Here is that line broken down into much more discrete steps.
c = 1 --start with a color of 1 low_dist = min(23-v,17-h) --get the lower inverted distance from center val = low_dist % 5 --mod 5 to bring it to a repeating range of 0 to 5 val = val / 3 --divide by 3. value is now 0 to 1.66 val = flr(val) --round it down. value is now 0 or 1 val = val * 6 --multiply by 6. value is now 0 or 6 c += val --add value to c, making it 1 or 7
The first thing I do is c=1. That means the entire rest of the line will either add 0 or 6 (bumping the value up to 7). No other outcome is acceptable. min(23-v,17-h)%5/3\1*6 will always evaluate to 0 or 6.
I only want the lower value of h and v. This is what will give it the nice box shape. If you color the points inside a rectangle so that ones that are closer to the center on their X are one color and ones that are closer to the center on their Y are a different color you'll get a pattern with clean diagonal lines running from the center towards the corners like this:
Tumblr media
You might think I would just use min(v,h) instead of the longer min(23-v,17-h) in the actual code. I would love to do that, but it results in a pattern that is cool, but doesn't really look like a card back.
Tumblr media
I take the inverted value. Instead of having a v that runs from 0 to 23, I flip it so it runs from 23 to 0. I do the same for h. I take the lower of those two values using min().
Then I use modulo (%) to bring the value to a repeating range of 0 to 5. Then I divide that result by 3 so it is 0 to ~1.66. The exact value doens't matter too much because I am going round it down anyway. What is critical is that it will become 0 or 1 after rounding because then I can multiply it by a specific number without getting any values in between.
Wait? If I'm rounding down, where is flr() in this line: c=1+min(23-v,17-h)%5/3\1*6?
It's not there! That's because there is a sneaky tool in Pico-8. You can use \1 to do the same thing as flr(). This is integer division and it generally saves a 3 characters.
Finally, I multiply the result by 6. If it is 0, we get 0. If it is 1 we get 6. Add it to 1 and we get the color we want!
Here's how it looks with each step in that process turned on or off:
Tumblr media
A Note About Parentheses
When I write tweetcarts I would typically start by writing this type of line like this: c=1+ (((min(23-v,17-h)%5)/3) \1) *6
This way I can figure out if my math makes sense by using parentheses to ensure that my order of operations works. But then I just start deleting them willy nilly to see what I can get away with. Sometimes I'm surprised and I'm able to shave off 2 characters by removing a set of parentheses.
The Face Side
The face side with the diamond and the "A" is a little more complex, but basically works the same way as the back. Each pixel needs to either be white (7) or red (8). When the card is on this side, I'll be overwriting the c value that got defined earlier.
Tumblr media
Here's the code that does it (with added white space). This uses the h and v values defined earlier as well as the r and p values from the nested loops.
u=(r-1)/80 z=a(p-.2) if(e%1<.5) c= a(r-5) < 5 and z < u+.03 and (r==5 or z>u) and 8 or 8-sgn(h+v-9)/2
Before we piece out what this is doing, we need to talk about the structure for conditional logic in tweetcarts.
The Problem with If Statements
The lone line with the if statement is doing a lot of conditional logic in a very cumbersome way designed to avoid writing out a full if statement.
One of the tricky things with Pico-8 tweetcarts is that the loop and conditional logic of Lua is very character intensive. While most programming language might write an if statement like this:
if (SOMETHING){ CODE }
Lua does it like this:
if SOMETHING then CODE end
Using "then" and "end" instead of brackets means we often want to bend over backwards to avoid them when we're trying to save characters.
Luckily, Lua lets you drop "then" and "end" if there is a single command being executed inside the if.
This means we can write
if(e%1 < 0.5) c=5
instead of
if e%1 < 0.5 then c=5 end
This is a huge savings! To take advantage of this, it is often worth doing something in a slightly (or massively) convoluted way if it means we can reduce it to a single line inside the if. This brings us to:
Lua's Weird Ternary Operator
In most programming language there is an inline syntax to return one of two values based on a conditional. It's called the Ternary Operator and in most languages I use it looks like this:
myVar = a>b ? 5 : 10
The value of myVar will be 5 if a is greater than b. Otherwise is will be 10.
Lua has a ternary operator... sort of. You can read more about it here but it looks something like this:
myVar = a>b and 5 or 10
Frankly, I don't understand why this works, but I can confirm that it does.
In this specific instance, I am essentially using it to put another conditional inside my if statement, but by doing it as a single line ternary operation, I'm keeping the whole thing to a single line and saving precious chars.
The Face Broken Out
The conditional for the diamond and the A is a mess to look at. The weird syntax for the ternary operator doesn't help. Neither does the fact that I took out any parentheses that could make sense of it.
Here is the same code rewritten with a cleaner logic flow.
--check time to see if we're on the front half if e%1 < .5 then --this if checks if we're in the A u=(r-1)/80 z=a(p-.2) if a(r-5) < 5 and z < u+.03 and (r==5 or z>u) then c = 8 --if we're not in the A, set c based on if we're in the diamond else c = 8-sgn(h+v-9)/2 end end
The first thing being checked is the time. As I explained further up, because the input value for sin() in Pico-8 goes from 0 to 1, the midpoint is 0.5. We only draw the front of the card if e%1 is less than 0.5.
After that, we check if this pixel is inside the A on the corner of the card or the diamond. Either way, our color value c gets set to either 7 (white) or 8 (red).
Let's start with diamond because it is easier.
The Diamond
This uses the same h and v values from the back of the card. The reason I chose diamonds for my suit is that they are very easy to calculate if you know the vertical and horizontal distance from a point! In fact, I sometimes use this diamond shape instead of proper circular hit detection in size-coded games.
Let's look at the line: c = 8-sgn(h+v-9)/2
This starts with 8, the red color. Since the only other acceptable color is 7 (white), tha means that sgn(h+v-9)/2 has to evaluate to either 1 or 0.
sgn() returns the sign of a number, meaning -1 if the number is negative or 1 if the number is positive. This is often a convenient way to cut large values down to easy-to-work-with values based on a threshold. That's exactly what I'm doing here!
h+v-9 takes the height from the center plus the horizontal distance from the center and checks if the sum is greater than 9. If it is, sgn(h+v-9) will return 1, otherwise -1. In this formula, 9 is the size of the diamond. A smaller number would result in a smaller diamond since that's the threshold for the distance being used. (note: h+v is NOT the actual distance. It's an approximation that happens to make a nice diamond shape.)
OK, but adding -1 or 1 to 8 gives us 7 or 9 and I need 7 or 8.
That's where /2 comes in. Pico-8 defaults to floating point math, so dividing by 2 will turn my -1 or 1 into -0.5 or 0.5. So this line c = 8-sgn(h+v-9)/2 actually sets c to 7.5 or 8.5. Pico-8 always rounds down when setting colors so a value of 7.5 becomes 7 and 8.5 becomes 8. And now we have white for most of the card, and red in the space inside the diamond!
The A
The A on the top corner of the card was the last thing I added. I finished the spinning card with the card back and the diamond and realized that when I condensed the whole thing, I actually had about 50 characters to spare. Putting a letter on the ace seemed like an obvious choice. I struggled for an evening trying to make it happen before deciding that I just couldn't do it. The next day I took another crack at it and managed to get it in, although a lot of it is pretty ugly! Luckily, in the final version the card is spinning pretty fast and it is harder to notice how lopsided it is.
I mentioned earlier that my method of placing pixels in a line between points is great for deforming planes, but makes a lot of drawing harder. Here's a great example. Instead of just being able to call print("a") or even using 3 calls to line() I had to make a convoluted conditional to check if each pixel is "inside" the A and set it to red if it is.
I'll do my best to explain this code, but it was hammered together with a lot of trial and error. I kept messing with it until I found an acceptable balance between how it looked and how many character it ate up.
Here are the relevant bits again:
u=(r-1)/80 z=a(p-.2) if a(r-5) < 5 and z < u+.03 and (r==5 or z>u) then c = 8
The two variables above the if are just values that get used multiple times. Let's give them slightly better names. While I'm making edits, I'll expand a too since that was just a replacement for abs().
slope = (r-1)/80 dist_from_center = abs(p-.2) if abs(r-5) < 5 and dist_from_center < slope+.03 and (r==5 or dist_from_center>slope) then c = 8
Remember that r is the current row and p is the percentage of the way between the two sides where this pixel falls.
u/slope here is basically how far from the center line of the A the legs are at this row. As r increases, so does slope (but at a much smaller rate). The top of the A is very close to the center, the bottom is further out. I'm subtracting 1 so that when r is 0, slope is negative and will not be drawn. Without this, the A starts on the very topmost line of the card and looks bad.
z/dist_from_center is how far this particular p value is from the center of the A (not the center of the card), measured in percentage (not pixels). The center of the A is 20% of the way across the card. This side of the card starts on the right (0% is all the way right, 100% is all the way left), which is why you see the A 20% away from the right side of the card.
Tumblr media
These values are important because the two legs of the A are basically tiny distance checks where the slope for a given r is compared against the dist_from_center. There are 3 checks used to determine if the pixel is part of the A.
if a(r-5) < 5 and z < u+.03 and (r==5 or z>u) then
The first is abs(r-5) < 5. This checks if r is between 1 and 9, the height of my A.
The second is dist_from_center < slope+.03. This is checking if this pixel's x distance from the center of the A is no more than .03 bigger than the current slope value. This is the maximum distance that will be considered "inside" the A. All of this is a percentage, so the center of the A is 0.20 and the slope value will be larger the further down the A we get.
Because I am checking the distance from the center point (the grey line in the image above), this works on either leg of the A. On either side, the pixel can be less than slope+.03 away.
Finally, it checks (r==5 or dist_from_center>slope). If the row is exactly 5, that is the crossbar across the A and should be red. Otherwise, the distance value must be greater than slope (this is the minimum value it can have to be "inside" the A). This also works on both sides thanks to using distance.
Although I am trying to capture 1-pixel-wide lines to draw the shape of the A, I could not think of a cleaner way than doing this bounding check. Ignoring the crossbar on row 5, you can think about the 2nd and 3rd parts of the if statement essentially making sure that dist_from_center fits between slope and a number slightly larger than slope. Something like this:
slope < dist_from_center < slope+0.03
Putting it Together
All of this logic needed to be on a single line to get away with using the short form of the if statement so it got slammed into a single ternary operator. Then I tried removing parentheses one at a time to see what was structurally significant. I wish I could say I was more thoughtful than that but I wasn't. The end result is this beefy line of code:
if(e%1<.5)c=a(r-5)<5and z<u+.03and(r==5or z>u)and 8or 8-sgn(h+v-9)/2
Once we've checked that e (our time value) is in the phase where we show the face, the ternary operator checks if the pixel is inside the A. If it is, c is set to 8 (red). If it isn't, then we set c = 8-sgn(h+v-9)/2, which is the diamond shape described above.
That's It!
Once we've set c the tweetcart uses pset to draw the pixel as described in the section on drawing the lines.
Here's the full code and what it looks like when it runs again. Hopefully now you can pick out more of what's going on!
a=abs::_::cls()e=t()for r=0,46do for p=0,1,.025do j=sin(e)*20k=cos(e)*5f=1-p h=a(17-p*34)v=a(23-r)c=1+min(23-v,17-h)%5/3\1*6u=(r-1)/80z=a(p-.2)if(e%1<.5)c=a(r-5)<5and z<u+.03and(r==5or z>u)and 8or 8-sgn(h+v-9)/2 g=r+39pset((64+j)*p+(64-j)*f,(g+k)*p+(g-k)*f,c)end end flip()goto _
Tumblr media
I hope this was helpful! I had a lot of fun writing this cart and it was fun to break it down. Maybe you can shave off the one additional character needed to slow it down by using e=t()/2 a bit. If you do, please drop me a line on my mastodon or tumblr!
And if you want to try your hand at something like this, consider submitting something to TweetTweetJam which just started! You'll get a luxurious 500 characters to work with!
Links and Resources
There are some very useful posts of tools and tricks for getting into tweetcarts. I'm sure I'm missing many but here are a few that I refer to regularly.
Pixienop's tweetcart basics and tweetcart studies are probably the single best thing to read if you want to learn more.
Trasevol_Dog's Doodle Insights are fascinating, and some of them demonstrate very cool tweetcart techniques.
Optimizing Character Count for Tweetcarts by Eli Piilonen / @2DArray
Guide for Making Tweetcarts by PrincessChooChoo
The official documentation for the hidden P8SCII Control Codes is worth a read. It will let you do wild things like play sound using the print() command.
I have released several size-coded Pico-8 games that have links to heavily annotated code:
Pico-Mace
Cold Sun Surf
1k Jump
Hand Cram
And if you want to read more Pico-8 weirdness from me, I wrote a whole post on creating a networked Pico-8 tribute to Frog Chorus.
13 notes · View notes
otesunki · 3 months
Text
i am going to Shamelessly advertise my article on my blog because this is My house
additionally where is forthblr i am so disappointed in y'all
3 notes · View notes
b8horpet · 8 months
Text
youtube
5 notes · View notes
b9horpet · 1 year
Text
#codetober 2023. 10. 09.
Little progress sadly, had a busy day.
Tried the graphics pipeline and of course it didn't work for the first time. Some googling yielded the solution: have to force the usage of vulkan rendering.
Now this out of the way I have the starting point to actually visually observe what the code is doing with the entities.
What kind of stuff do you like to program?
Mostly I'm interested in simulating things. Physics bodies, agents in an environment, particles, you get the idea.
Also I am interested in game development but have little experience in it.
Sizecoding is also a thing but not really a theme rather a format.
4 notes · View notes
Note
shdjflshfklasdhfakfh was it these?? uniqlo.com/sg/en/products/E456493-000?colorCode=COL22&sizeCode=SMA002 (also, I love how southeast asians will just be like "uniqlo 💖✌️"
CRYING. YES THESE WERE THE ONES MY DAD ALMOST BOUGHT THEM
2 notes · View notes
mialinks · 5 months
Text
Tumblr media
Narrow Strap Sandals, Uniqlo https://www.uniqlo.com/ph/en/products/E464755-000?colorCode=COL01&sizeCode=SMA004
0 notes
jobecreatesagame · 3 years
Text
I'll try to do something different
Hi. I'm Jori, also known as jobe. You might know me from the demoscene releases I've made for the PICO-8 and TIC-80 fantasy platforms. That all started from a desire to do something different. I had a PICO-8 license to create music for other coders and their demos, but those demos never really materialized. Which, let me just state outright, I have no hard feelings about. Creating things is hard, creating things and balancing that with other things going on in your life is very hard, so I fully understand that hobby projects don't always get finished. I know mine don't.
But I had this tool, and I wanted to work on demos, and since no one else would make my demos for me, why not make a demo myself? How hard can that be?
Long story short, surprisingly hard in some aspects, surprisingly easy in others, and in the end, I had created something people seemed to like.
youtube
A couple of things that people seemed to like.
But doing the same thing over and over again can put you in a bit of a rut, even if you're getting better at the thing you're doing. I keep thinking it would be fun to make another PICO-8 demo, but after Puroresu no Seishin, I don't know where I would go from that. I feel like I've done everything I want to do on that platform.
I looked at another platform. TIC-80 was the logical next step, as it's also a fantasy platform, also Lua based, but with less technical restrictions. Less of a challenge, but that also meant I could do things that weren't feasible within PICO-8's virtual CPU cycle limit.
There's another difference. The cart format. There are two ways to release a PICO-8 cart - an uncompressed text file with the source code, or a .p8.png file that contains a label image. A TIC-80 cart, however, doesn't necessarily contain anything else than compressed source code. A seemingly minor detail but one that has huge implications for sizecoding. It's not impossible to do a tiny production on the PICO-8, as this 256 byte intro by Marquee Design illustrates, but as you can see from this TIC-80 256 byte intro also by Marquee Design, there's a world of difference between 256 bytes of compressed an uncompressed code.
Last September, I made my first 256 byte intro. I had no idea I would enjoy sizecoding as much as I do. It's a different kind of challenge from making a demo, as you hit the wall almost immediately and have to start thinking of the size limit from the first character of code you write. It's also much less of an undertaking than a full demo project - a way to quickly express one audiovisual idea and be done with it. I'll be doing more sizecoding later for sure. Lovebyte, an online demoscene event, is taking place a month from now and I'll definitely try to get something done for the event.
There's one other thing I hadn't done before 2021. I hadn't made a game.
That's not entirely true, of course. After all, I did work in game development for six years. The problem with the auteur theory is that attributing the whole creative process to a Ken Levine or a Hidetaka Miyazaki does a massive disservice to everyone else involved in creating a Bioshock or a Dark Souls.
Tumblr media
Having both contributed to demos as team efforts and created them all by myself, however, to me the solo efforts have felt more like "my" demos. And of course, there are games like Undertale and Axiom Verge where the entirety or overwhelming majority of the game is created by a single developer, resulting in a highly personal expression of that developer's vision.
I finished "my" first game about a month ago. Deckpest, a combination of a deckbuilder and a Tempest-style arcade shooter, made for the TIC-80. A course work for school, Deckpest is almost entirely a solo creation - another person on the course made the music, while I made the graphics and designed and coded the game. The purpose of the course was to learn project management, and it was very successful at that. I don't know if I could have finished the project if I didn't adopt Kanban.
The scope of Deckpest was limited by the strict course deadline, but it was also limited by the platform. While there are no CPU cycle or token amount restrictions, TIC-80 still restricts you to 64 kilobytes of written code. Currently, Deckpest is at 63629 bytes. I managed this without any size optimization, but if I wanted to expand the game any more, I would have to find ways to cram more code into the cart, probably sacrificing readability and maintainability.
But I enjoy the core gameplay loop of Deckpest so much that I feel like I could do something more with it. Make it bigger.
So I'm taking it to LÖVE.
Tumblr media
LÖVE is a free and open source game development framework. It's the perfect choice for building upon Deckpest. It's designed for 2D games, so it's very suitable for a 2.5D game like Deckpest. It uses Lua, so I've actually been able to port some of the code from Deckpest almost directly. It's multi-platform, and while I'm not making any promises (I don't have a Mac to test things on so getting the shaders to work on Mac OS X might be a bit difficult), I'll at least try to bring it to other platforms than just Windows.
It's going to be a big project, one that will have to take lower priority to all kinds of adult responsibilities. The full scope of the thing is a bit nebulous this point. My uneducated estimate is that I'll have version 1.0 out sometime near the end of 2023, and until then, it's going to be my main creative outlet. So, don't expect any demos from me any time soon. I won't be abandoning demoscene activities completely, as sizecoding and live coding are great, less time consuming ways for me to express myself, but I won't be committing myself to creating a full demo for now.
Why, then, am I writing this instead of working on the game? Because for me, creating is communicating, and I don't see why creating a game would be any different from creating a demo. However, I am still far from having any kind of playable preview I could comfortably hand to more than very few people. I feel that if I blog about my process, it will feel more like I'm communicating. It will feel less lonely.
So, if you're still reading this, thank you for letting me communicate to you. See you in the next post!
1 note · View note
newseveryhourly · 6 years
Link
SizeCoding.org is a wiki dedicated to the art of creating very tiny programs for the 80x86 family of CPUs. By "very tiny programs", we mean programs that are 256 bytes or less in size, typically created by members of the demoscene as a show of programming skill. The size of these tiny programs is me... https://ift.tt/2br02O2
0 notes
Only cause I worked so hard on it LOL 🔴 - Reasonable Stuff LOL (White - XS) https://www.victoriassecret.com/bras/shop-all-bras-mobile/scalloped-crochet-bralette-the-victorias-secret-bralette-collection?ProductID=314415&CatalogueType=OLS (Black - XS) https://www.victoriassecret.com/lingerie/shop-all-lingerie-mobile/long-sleeve-plunge-teddy-beautiful-by-victorias-secret?ProductID=312574&CatalogueType=OLS (Black - XS) https://www.victoriassecret.com/lingerie/shop-all-lingerie-mobile/lace-plunge-teddy-dream-angels?ProductID=309148&CatalogueType=OLS (Candy Apple - Short, XS) https://www.victoriassecret.com/sleepwear/shop-all-sleep-mobile/the-sleepover-knit-pajama?ProductID=302510&CatalogueType=OLS (Black - XS) https://www.victoriassecret.com/pink/all-apparel/reversible-bomber-jacket-pink?ProductID=313417&CatalogueType=OLS (Black) https://www.victoriassecret.com/pink/all-active-pink/metal-water-bottle-pink?ProductID=314712&CatalogueType=OLS (Soft Begonia) https://www.victoriassecret.com/pink/shop-all-accessories/wheelie-bag-pink?ProductID=314901&CatalogueType=OLS 🔴 (Tan - 7) http://m.dsw.com/shop/product/362443/?colorCode=001&sizeCode=7.0&widthCode=M (Either color - S) http://www.target.com/p/-/A-51308131 (Grey - S) https://m.hollisterco.com/shop/us/girls-gift-guide-6898233/off-the-shoulder-ribbed-sweater-8295730_03 (Perfection or Drama, I already have Adored) https://www.victoriassecret.com/beauty/lip/velvet-matte-cream-lip-stain-victorias-secret?ProductID=311968&CatalogueType=
0 notes
andymakesgames · 5 months
Text
Tumblr media
stary sky / diamond eyes 280 bytes
(there's room to further minimize but I'm happy to keep it halfway readable)
::❖:: cls() p=t()%1 for c=0,9do for r=0,9do x=c*16-32*p y=r*16-32*p+(c%2)*8 pset(x+8,y-p*16,5) m=min(7+sin(y/(70+sin(t()/3)*30))*3,(64-abs(x-64))/4)\1 for d=1,m do line() for i=0,1,.25do line(x+cos(i)*d,y+sin(i)*d,d<m and 7+(d/2)%2or 2+(m/8)\1*10) end end end end flip()goto ❖
11 notes · View notes
andymakesgames · 4 months
Text
Tumblr media
Seneca 279 bytes
h=128::_::?'⁶1⁶c' k=0for i=1,50,7do for s=0,6,i/220do k+=1z=h-i p=-(t()/(2+i/15)+s)%6x=p<2and i or z a=p<1and i or z y=p<1and h or i b=p<2and i or h c=p>1 and p<2 and i==8and 12or 6 if(p<3)p=p%1r=1-p?("cruelty springs from weakness ")[31-k%31],r*x+p*a-1,r*y+p*b,c end end goto _
7 notes · View notes
andymakesgames · 5 months
Text
Tumblr media
Finally started playing that poker rougelike! If you got mad about the source code of that game, wait until you see how I'm rendering a playing card.
279 bytes
a=abs::_::cls()e=t()for r=0,46do for p=0,1,.025do j=sin(e)*20k=cos(e)*5f=1-p h=a(17-p*34)v=a(23-r)c=1+min(23-v,17-h)%5/3\1*6u=(r-1)/80z=a(p-.2)if(e%1<.5)c=a(r-5)<5and z<u+.03and(r==5or z>u)and 8or 8-sgn(h+v-9)/2 g=r+39pset((64+j)*p+(64-j)*f,(g+k)*p+(g-k)*f,c)end end flip()goto _
7 notes · View notes
andymakesgames · 5 months
Text
Tumblr media
Watching pannenkoek2012's newest Mario 64 video has me thinking about low-res shapes.
Here's a really nasty spinning cube in exactly 280 chars. Using tables here is so gross. Somebody smarter than me please do this better.
x={0,0,0}y={}::_::cls()for i=0,4do j=i-1e=i/4+t()/5x[i]=64+sin(e)*40y[i]=40+cos(e)*9a=x[i]b=x[j%4]c=y[i]d=y[j%4] if a<b then for k=0,50do line(a,c+k,b,d+k,i)end end end h=x[0]-x[1]v=y[0]-y[1]for p=0,1,.01do a=x[1]*p+x[2]*(1-p)b=y[1]*p+y[2]*(1-p)line(a,b,a+h,b+v,9)end flip()goto _
10 notes · View notes
andymakesgames · 6 months
Text
Tumblr media
t=0 ::꩜:: t+=1/30 cls(9) camera(-64,-64) for k=0,9do for i=0,1,.02do a=k+t%2 s=mid(4,6,(5+sin((a+t)/8)*3)) d=a*(5+a)+sin(a+i*s+t)*(2+a) x=cos(i)*d y=sin(i)*d if(a>0)circfill(x,y,2+d/5,8+k%2) end end flip() goto ꩜
7 notes · View notes
andymakesgames · 11 days
Text
1K Pac-Man for Pico1K Jam
My entry to Pico1K Jam this year is a recreation of Pac-Man with just 1020 bytes of source code!
Tumblr media
The full source comes to 1020 bytes of #pico8 code.
The part that I am most proud of is that all ghost behavior logic is faithful to the original.
You can play it here.
That page contains a link to heavily commented source code if you are curious about how it works!
One interesting thing about these is figuring out where to push polish vs functionality.
Most people will only see the gif. Most people who play will play exactly once. It's important to me that the game feel good, but optimizing for a good demo is usually where it's at.
Ex: I decided that scatter mode would be nice, but burning a ton of chars on the waka-waka mouth was the better choice.
Although I will say that Cold Sun Surf, my entry to this jam from 2 years ago is, a genuinely fun arcade game. I'm very pleased with how that one plays.
2 notes · View notes
andymakesgames · 4 months
Text
Tumblr media
I was recently invited to submit a single-page code poem for Our Generation, published by Bad Quarto.
Here's my contribution! A massive single line of javascript called Efficient Allocation of Resources!
4 notes · View notes