{"author_link":"\/users\/alec","author_name":"alec","author_uid":"alec","comments":[],"epoch":1713637523,"event":"LD55","format":"md","ldjam_node_id":394336,"likes":11,"metadata":{"p_key":"206478","p_author":"alec","p_authorkey":"1013678","p_urlkey":"443012","p_title":"Anotomy of a Card in TIC-80","p_cat":"LDJam ","p_event":"LD55","p_time":"1713637523","p_likes":"11","p_comments":"0","p_status":"WAYBACK","us_key":"1013678","us_name":"alec","us_username":"alec","event_start":"1712966400","event_key":"120","event_name":"Ludum Dare 55"},"node":{"_collation":{"body_sanitizer":"TextUtils::SanitizeHTML via existing importer","event":"LD55","removed_author":false},"_superparent":383062,"_trust":7,"author":13678,"body":"Another Ludum Dare has come and gone! This time I teamed up with [Whaies](https:\/\/ldjam.com\/users\/whaies) and we created a game about summoning lawyers and other ... things ... to court. You can play it [here](https:\/\/ldjam.com\/events\/ludum-dare\/55\/upcard-writ). \n\n![video11.gif](\/\/\/raw\/e65\/3\/z\/64907.gif)\n\n---\n\nUpcard write is written in the [Janet Programming Langauge](https:\/\/janet-lang.org\/) using [tic80](https:\/\/tic80.com\/). I had a lot of fun figuring out the code for Cards. I wanted them to lift, rotate, and shuffle like the real thing and I think the solution I came up with in TIC-80 was pretty neat! Here's a technically rundown \n\nAnybody familiar with TIC-80 will know the basic way to draw a sprite is with the `spr` function. You give it an index in the sprite sheet and [x,y] coordinates and your done. But what if you want to do something crazy like rotate the sprite?! For that we must somehow implement [affine transformation](https:\/\/en.wikipedia.org\/wiki\/Affine_transformation).\n\nThe way everyone in the TIC-80 community has done this is using the `ttri`, textured triangle, function. I actually [wrote about this a couple years ago](https:\/\/alectroemel.com\/posts\/ludum-dare-51.html)! The basic idea is to use 2 textured triangles to draw a rectangle, and by manipulating the 4 coordinates using *math* you can do all sorts of fun transformations. Here's what that look likes like in Janet.\n\n```\n# adapted from https:\/\/cxong.github.io\/tic-80-examples\/affine-sprites\n\n(defn deg2rad [theta] (* theta PI_OVER_180))\n(defn rad2deg [theta] (* theta ONE_EIGHTY_OVER_PI))\n\n(defn aspr\/rotate [x y ca sa]\n  [(- (* x ca) (* y sa))\n   (+ (* x sa) (* y ca))])\n\n(defn aspr [x y &named\n\t    u1 v1\n\t    texsrc chromakey\n\t    sx sy flip rotate w h\n\t    ox oy shx1 shy1 shx2 shy2]\n  (default u1 0)\n  (default v1 0)\n  (default texsrc TEXSRC_SPR)\n  (default chromakey -1)\n  (default sx 1)\n  (default sy 1)\n  (default flip 0)\n  (default rotate 0)\n  (default w 1)\n  (default h 1)\n  (default ox (math\/floor (\/ (* w 8) 2)))\n  (default oy (math\/floor (\/ (* h 8) 2)))\n  (default shx1 0)\n  (default shy1 0)\n  (default shx2 0)\n  (default shy2 0)\n\n  (let [sx (if (= 1 (% flip 2)) (* -1 sx) sx)\n\tsy (if (> flip 2) (* -1 sy) sy)\n\tox (* -1 ox sx)\n\toy (* -1 oy sy)\n\n\t# Shear & rotate\n\tshx1 (* -1 shx1 sx)\n\tshy1 (* -1 shy1 sy)\n\tshx2 (* -1 shx2 sx)\n\tshy2 (* -1 shy2 sy)\n\trr rotate\n\tca (math\/cos rr)\n\tsa (math\/sin rr)\n\t[rx1 ry1] (aspr\/rotate (+ ox shx1) (+ oy shy1) ca sa)\n\t[rx2 ry2] (aspr\/rotate (+ ox shx1 (* w 8 sx)) (+ oy shy2) ca sa)\n\t[rx3 ry3] (aspr\/rotate (+ ox shx2) (+ oy shy1 (* h 8 sy)) ca sa)\n\t[rx4 ry4] (aspr\/rotate (+ ox shx2 (* w 8 sx)) (+ oy shy2 (* h 8 sy)) ca sa)\n\t[x1 y1] [(+ x rx1) (+ y ry1)]\n\t[x2 y2] [(+ x rx2) (+ y ry2)]\n\t[x3 y3] [(+ x rx3) (+ y ry3)]\n\t[x4 y4] [(+ x rx4) (+ y ry4)]\n\n\t# UV coords\n\tu2 (+ u1 (* w 8))\n\tv2 (+ v1 (* h 8))]\n    (tic80\/ttri x1 y1 x2 y2 x3 y3 u1 v1 u2 v1 u1 v2 texsrc chromakey)\n    (tic80\/ttri x3 y3 x4 y4 x2 y2 u1 v2 u2 v2 u2 v1 texsrc chromakey)))\n```\n\n> Sidenote: that this implementation does not use matrices, which maybe means \"affine\" is the wrong technical term?\n\nWith this function we can draw a sprite and rotate\/scale it however we need.\n\n![upcard-writ-1.gif](\/\/\/raw\/e65\/3\/z\/64903.gif)\n\nAwesome!\n\nHOWEVER, there's one problem. This rotates sprites, but how do we transform text? I didn't want to waste space in the spritesheet embedding letters, and TIC-80's `print` function definitely *does not* support rotating.\n\nThe Solution is to ~~abuse~~ use [VRAM](https:\/\/github.com\/nesbox\/TIC-80\/wiki\/ram). The video ram in TIC-80 is \"double banked\", which means you basically have 2 screens which are drawn over each other to work with. Conveniently, `ttri` supports pulling the texture from various different places. Those are the spritesheet, tilemap, and *the VRAM you're currently not drawing to*. With this we can\n\n\n1. switch to \n2. draw out the entire card using boring functions. Don't worry about any sort of rotation or scaling at this point.\n3. switch to `VRAM 1`\n4. clear the screen to hide everything on `VRAM 0`\n5. use our custom `sspr`function with `VRAM 0` as our texture source to draw our card with scale and rotation!\n\nHere's the annotated card drawing source code in Upcard Writ\n\n```\n# begin drawing in vbank 0\n(tic80\/vbank 0)\n(tic80\/cls 0)\n\n# draw empty base of card, which is in the map\n# I palette swap based on its type... maybe more on that in a future blog post\n(with-pallete-swap (match (character :integrity)\n\t\t\t :virtuous {5 14}\n\t\t\t :pragmatic {5 9}\n\t\t\t :sleazy {5 4})\n      (tic80\/map 0 0  6 10  0 0  0))\n\n# only face up cards need to have their details drawn\n(when flipped?\n      # print out title\n      (print-centered (character :name) 24 4 1 false 1 true)\n      (print-centered (character :name) 24 3 7 false 1 true)\n\n      # draw the cards picture if it has one\n      (when (character :sprite)\n\t(tic80\/rect 0 11 48 32 (character :sprite-bg))\n\t(tic80\/spr (character :sprite) ;(character :sprite-args))\n\t(tic80\/rectb 0 11 48 32 7))\n\n      # draw the cards \"resources\"\n      # ... theres some annoying logic to split it into 2 rows\n      (loop  [[i [mod ev]] :pairs (array\/slice (character :evidence) 0\n\t\t\t\t\t (min 2 (length (character :evidence))))]\n\t(evidence-spr ev (+ 8 (* i 22)) 43)\n\t(tic80\/spr (match mod :+ 1 :- 2) (+ 3 (* i 22)) 43 0))\n      (when (> (length (character :evidence)) 2)\n\t(loop  [[i [mod ev]] :pairs (array\/slice (character :evidence) 2)]\n\t  (evidence-spr ev (+ 8 (* i 22)) 60)\n\t  (tic80\/spr (match mod :+ 1 :- 2) (+ 3 (* i 22)) 57 0))))\n\n# some other stuff happens...\n# eventually we switch to vbank 1\n(tic80\/vbank 0)\n(tic80\/cls 0)\n\n# and Finally draw the card with rotation and scale!\n(aspr ;(round (- (self :pos) [0 (self :height)]))\n\t  :u1 0 :v1 0\n\t  :w 6 :h 10\n\t  :texsrc TEXSRC_VRAM\n\t  :sx (self :scale) :sy (self :scale)\n\t  :rotate (self :rotation))\n```\n\nPhew! Here's the results (I've removed a screen clear so you can see both vrams).\n\n![upcard-writ-2.gif](\/\/\/raw\/e65\/3\/z\/64902.gif)\n\nIf nothing else, maybe all this has inspired you to take a look at [tic80](https:\/\/tic80.com\/), its pretty neat.\n\nRegardless, thanks for reading!\n","comments":2,"comments-timestamp":"2024-04-20T18:51:46Z","created":"2024-04-20T18:11:31Z","files":[],"files-timestamp":0,"id":394336,"love":11,"love-timestamp":"2024-04-21T13:12:46Z","meta":[],"modified":"2024-04-21T13:12:46Z","name":"Anotomy of a Card in TIC-80","node-timestamp":"2024-04-20T18:30:07Z","parent":383234,"parents":[1,5,9,383062,383234],"path":"\/events\/ludum-dare\/55\/upcard-writ\/anotomy-of-a-card-in-tic-80","published":"2024-04-20T18:25:23Z","scope":"public","slug":"anotomy-of-a-card-in-tic-80","subsubtype":"","subtype":"","type":"post","version":1234955},"node_metadata":{"n_key":"394336","n_urlkey":"443012","n_parent":"383234","n_path":"\/events\/ludum-dare\/55\/upcard-writ\/anotomy-of-a-card-in-tic-80","n_slug":"anotomy-of-a-card-in-tic-80","n_type":"post","n_subtype":"","n_subsubtype":"","n_author":"13678","n_created":"1713636691","n_modified":"1713705166","n_version":"1234955","n_status":"WAYBACK"},"source_url":"https:\/\/ldjam.com\/events\/ludum-dare\/55\/upcard-writ\/anotomy-of-a-card-in-tic-80","text":"Another Ludum Dare has come and gone! This time I teamed up with [Whaies](https:\/\/ldjam.com\/users\/whaies) and we created a game about summoning lawyers and other ... things ... to court. You can play it [here](https:\/\/ldjam.com\/events\/ludum-dare\/55\/upcard-writ). \n\n![video11.gif](\/\/\/raw\/e65\/3\/z\/64907.gif)\n\n---\n\nUpcard write is written in the [Janet Programming Langauge](https:\/\/janet-lang.org\/) using [tic80](https:\/\/tic80.com\/). I had a lot of fun figuring out the code for Cards. I wanted them to lift, rotate, and shuffle like the real thing and I think the solution I came up with in TIC-80 was pretty neat! Here's a technically rundown \n\nAnybody familiar with TIC-80 will know the basic way to draw a sprite is with the `spr` function. You give it an index in the sprite sheet and [x,y] coordinates and your done. But what if you want to do something crazy like rotate the sprite?! For that we must somehow implement [affine transformation](https:\/\/en.wikipedia.org\/wiki\/Affine_transformation).\n\nThe way everyone in the TIC-80 community has done this is using the `ttri`, textured triangle, function. I actually [wrote about this a couple years ago](https:\/\/alectroemel.com\/posts\/ludum-dare-51.html)! The basic idea is to use 2 textured triangles to draw a rectangle, and by manipulating the 4 coordinates using *math* you can do all sorts of fun transformations. Here's what that look likes like in Janet.\n\n```\n# adapted from https:\/\/cxong.github.io\/tic-80-examples\/affine-sprites\n\n(defn deg2rad [theta] (* theta PI_OVER_180))\n(defn rad2deg [theta] (* theta ONE_EIGHTY_OVER_PI))\n\n(defn aspr\/rotate [x y ca sa]\n  [(- (* x ca) (* y sa))\n   (+ (* x sa) (* y ca))])\n\n(defn aspr [x y &named\n\t    u1 v1\n\t    texsrc chromakey\n\t    sx sy flip rotate w h\n\t    ox oy shx1 shy1 shx2 shy2]\n  (default u1 0)\n  (default v1 0)\n  (default texsrc TEXSRC_SPR)\n  (default chromakey -1)\n  (default sx 1)\n  (default sy 1)\n  (default flip 0)\n  (default rotate 0)\n  (default w 1)\n  (default h 1)\n  (default ox (math\/floor (\/ (* w 8) 2)))\n  (default oy (math\/floor (\/ (* h 8) 2)))\n  (default shx1 0)\n  (default shy1 0)\n  (default shx2 0)\n  (default shy2 0)\n\n  (let [sx (if (= 1 (% flip 2)) (* -1 sx) sx)\n\tsy (if (> flip 2) (* -1 sy) sy)\n\tox (* -1 ox sx)\n\toy (* -1 oy sy)\n\n\t# Shear & rotate\n\tshx1 (* -1 shx1 sx)\n\tshy1 (* -1 shy1 sy)\n\tshx2 (* -1 shx2 sx)\n\tshy2 (* -1 shy2 sy)\n\trr rotate\n\tca (math\/cos rr)\n\tsa (math\/sin rr)\n\t[rx1 ry1] (aspr\/rotate (+ ox shx1) (+ oy shy1) ca sa)\n\t[rx2 ry2] (aspr\/rotate (+ ox shx1 (* w 8 sx)) (+ oy shy2) ca sa)\n\t[rx3 ry3] (aspr\/rotate (+ ox shx2) (+ oy shy1 (* h 8 sy)) ca sa)\n\t[rx4 ry4] (aspr\/rotate (+ ox shx2 (* w 8 sx)) (+ oy shy2 (* h 8 sy)) ca sa)\n\t[x1 y1] [(+ x rx1) (+ y ry1)]\n\t[x2 y2] [(+ x rx2) (+ y ry2)]\n\t[x3 y3] [(+ x rx3) (+ y ry3)]\n\t[x4 y4] [(+ x rx4) (+ y ry4)]\n\n\t# UV coords\n\tu2 (+ u1 (* w 8))\n\tv2 (+ v1 (* h 8))]\n    (tic80\/ttri x1 y1 x2 y2 x3 y3 u1 v1 u2 v1 u1 v2 texsrc chromakey)\n    (tic80\/ttri x3 y3 x4 y4 x2 y2 u1 v2 u2 v2 u2 v1 texsrc chromakey)))\n```\n\n> Sidenote: that this implementation does not use matrices, which maybe means \"affine\" is the wrong technical term?\n\nWith this function we can draw a sprite and rotate\/scale it however we need.\n\n![upcard-writ-1.gif](\/\/\/raw\/e65\/3\/z\/64903.gif)\n\nAwesome!\n\nHOWEVER, there's one problem. This rotates sprites, but how do we transform text? I didn't want to waste space in the spritesheet embedding letters, and TIC-80's `print` function definitely *does not* support rotating.\n\nThe Solution is to ~~abuse~~ use [VRAM](https:\/\/github.com\/nesbox\/TIC-80\/wiki\/ram). The video ram in TIC-80 is \"double banked\", which means you basically have 2 screens which are drawn over each other to work with. Conveniently, `ttri` supports pulling the texture from various different places. Those are the spritesheet, tilemap, and *the VRAM you're currently not drawing to*. With this we can\n\n\n1. switch to \n2. draw out the entire card using boring functions. Don't worry about any sort of rotation or scaling at this point.\n3. switch to `VRAM 1`\n4. clear the screen to hide everything on `VRAM 0`\n5. use our custom `sspr`function with `VRAM 0` as our texture source to draw our card with scale and rotation!\n\nHere's the annotated card drawing source code in Upcard Writ\n\n```\n# begin drawing in vbank 0\n(tic80\/vbank 0)\n(tic80\/cls 0)\n\n# draw empty base of card, which is in the map\n# I palette swap based on its type... maybe more on that in a future blog post\n(with-pallete-swap (match (character :integrity)\n\t\t\t :virtuous {5 14}\n\t\t\t :pragmatic {5 9}\n\t\t\t :sleazy {5 4})\n      (tic80\/map 0 0  6 10  0 0  0))\n\n# only face up cards need to have their details drawn\n(when flipped?\n      # print out title\n      (print-centered (character :name) 24 4 1 false 1 true)\n      (print-centered (character :name) 24 3 7 false 1 true)\n\n      # draw the cards picture if it has one\n      (when (character :sprite)\n\t(tic80\/rect 0 11 48 32 (character :sprite-bg))\n\t(tic80\/spr (character :sprite) ;(character :sprite-args))\n\t(tic80\/rectb 0 11 48 32 7))\n\n      # draw the cards \"resources\"\n      # ... theres some annoying logic to split it into 2 rows\n      (loop  [[i [mod ev]] :pairs (array\/slice (character :evidence) 0\n\t\t\t\t\t (min 2 (length (character :evidence))))]\n\t(evidence-spr ev (+ 8 (* i 22)) 43)\n\t(tic80\/spr (match mod :+ 1 :- 2) (+ 3 (* i 22)) 43 0))\n      (when (> (length (character :evidence)) 2)\n\t(loop  [[i [mod ev]] :pairs (array\/slice (character :evidence) 2)]\n\t  (evidence-spr ev (+ 8 (* i 22)) 60)\n\t  (tic80\/spr (match mod :+ 1 :- 2) (+ 3 (* i 22)) 57 0))))\n\n# some other stuff happens...\n# eventually we switch to vbank 1\n(tic80\/vbank 0)\n(tic80\/cls 0)\n\n# and Finally draw the card with rotation and scale!\n(aspr ;(round (- (self :pos) [0 (self :height)]))\n\t  :u1 0 :v1 0\n\t  :w 6 :h 10\n\t  :texsrc TEXSRC_VRAM\n\t  :sx (self :scale) :sy (self :scale)\n\t  :rotate (self :rotation))\n```\n\nPhew! Here's the results (I've removed a screen clear so you can see both vrams).\n\n![upcard-writ-2.gif](\/\/\/raw\/e65\/3\/z\/64902.gif)\n\nIf nothing else, maybe all this has inspired you to take a look at [tic80](https:\/\/tic80.com\/), its pretty neat.\n\nRegardless, thanks for reading!\n","title":"Anotomy of a Card in TIC-80","wayback_source":[]}