From 4a589ff5126e975295d241fe9c34652791789981 Mon Sep 17 00:00:00 2001 From: EehMauro Date: Mon, 13 Oct 2025 18:01:44 -0300 Subject: [PATCH 1/2] feat: add cardano summit token --- frontend/src/stores/challenge.ts | 5 +++ godot-visualizer/resources/token_cardano.png | Bin 0 -> 4361 bytes .../resources/token_cardano.png.import | 34 ++++++++++++++++++ godot-visualizer/scenes/token.tscn | 13 +++++-- 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 godot-visualizer/resources/token_cardano.png create mode 100644 godot-visualizer/resources/token_cardano.png.import diff --git a/frontend/src/stores/challenge.ts b/frontend/src/stores/challenge.ts index fce650b..f5066cb 100644 --- a/frontend/src/stores/challenge.ts +++ b/frontend/src/stores/challenge.ts @@ -52,6 +52,11 @@ export const challenges: Challenge[] = [{ network: 'mainnet', explorerUrl: 'https://cexplorer.io/tx/', tokens: [{ + name: 'cardano', + displayName: 'Cardano Summit 2025', + assetName: 'Cardano Summit 2025 discount shard', + policyId: '5fb286e39c3cda5a5abd17501c17b01987ebfa282df129c4df1bf27ee29ca82073756d6d6974203230323520646973636f756e74207368617264', + },{ name: 'iagon', displayName: 'IAGON', assetName: '$IAG', diff --git a/godot-visualizer/resources/token_cardano.png b/godot-visualizer/resources/token_cardano.png new file mode 100644 index 0000000000000000000000000000000000000000..feabcae46d84185b0ad302f66cf4618859d73180 GIT binary patch literal 4361 zcmbVQc{r49-yRcU+AJ-W%57AZn$;Lg#@LN&G+Byj%4`_R%$ONjl2l|3g(O?D6p3h& zB_vUZ7CYGsA-_!B-}c8HO13m#15tp0K%g}i zL=!9FJz)8g5EuSG(Wll6Z<1W1Js$*GCA)lyfUYLVfk2{jEE_w49cdqq&T)rR85|lD z?(NPMqCp@%f;X2+cV!9y8q9g z3@AY#(DTL#3EY_iD&Xzz#^&R^@z5Wmof+Lqz{QxrPe{ft+p4$)O3_618#&l=0 z1$-eE^#{v!<_I`^XU_jX{pa<+84yO7MEYanZ+&rh|6_tLFg+oZ@hc#Ii{{(-aG3}z zCZFTUqccrU2;Ed!R>Q>^^O#fthiAj#xc$x*`M1je8Vg6E05uYo&SEdCQ2&=ROcSbr ziH9yH4u(R)P?|O5)4~J~70QOQSPUGU zu8pN@qLDByM{P|QMhi`Y=`yKU7?OdeIb!KLbVoG(_jwZz-E%p)zt1!NpXc}USi=6I zy8XvI%U!oz6*wY`FU*(EucokOdi+|sv49_ifTPlv3mgxnFK3p?fc|>S`i~>(WH0BdP10KIN+qztHIw&|u z{7;axI_86cTQM&)ynz`>`ZzeAR(up$QzIVYc*q~Ny)sWFZ90j!<6fj^pb4m>>&8}z zq~X}?mylK|h{#$wI)9yL!zmS`Hpx3%o)jOG~{1 z7K+`B++*h%>@E#tl_9(5ZxPJK2YgQTQ%*nhAsBZTh{IFN$HzxMw^Tj~D>Vsdi_MK} zlUEGuns!bhZ#up zdV8TQ{lYulzLu9LGxv33!5arM#bpjmZrYxKjq6RuW-o!H!?PK5WVT36)_#3p9909q z>+igaa8C3}=lCL5(rZ!^djxsn>)yMrb<=ulPh20Gop(Cc#lIPvAO|^Y47&B`^fbFV z?^w5=zHe=Ny%NiUJBOjEJRNLbSmhLd?IFIbI`jTh@jHW65)no0(<$R4tcVy6^u)Za z58xy}WxrU#>`STXfvu?zn0+b}zQ2e-ms0~@_mb!%MiixInBRJwmP-$ikhv?ih_pUy z*WI-N)|V)Lr*q*sovM?HYpv_GCH`35#KY}2o_yCD8o>9?FG(5ZS~)%Oc}1wdBHxz%u`89&AO#9 zFmVze_mMpBh}lI|^Cn6!>9vQ>e=7|+{wQRg>j00~-Q)NiCCtzBll=aHce4(iK83Oe z?kg_HLD=J84IedDl*)%U^=e|jCVJAt*B?SaANYJRJGt7wF9V*opACe5J!CtcM0~q; zrG(7ri>`G$n{WH#1E18Y_nI5#)1#RnGlD*2&{FG)E+XfDd@($7&{DZk zWSAiz<)<9beW4x#bmy+meKo8NmaDnw@6r*#YO`&kzAK|GJ_rH4^!S#-!XY4Ddo7-E zDo41wX0iIx@~Yj+JN2uoFW(5U_;_kT0vPzL@w!#(LoBHUDlhnyeV8KIT#ITM#GjDO zclMi9FAGqSjaknm*8J6U8IP#HUo}Z=YgB+wDK#v;c;7OX6?Nu&-+ZJ|b#2mg=NKeg zqR%ZHLs9ZOJ$7R9MHphWwWl&U<3jq4-Q6G)mlalIry2IH5xdqWXR~O>N9{T0D-b^p|L^jAPyoA^I zRoy&BY0w$oZkfA9J9;y3;&_5$k18Ot~}Ud3ty0 z)<%*l0yT-TQ!EP6{id)fS?RQS-Q{oo@}u9s}+R*t7BeSVY$7-cJ_@r_Z zaBEDmnzrt&-fn{%lvQV-Ln|<7)b2@ftVi2G%qZ-L?e2wqeIM14d=2pq?kx9 z0lzmZaQmoTexY^M6eUF@c(MhP-?rgH&iBV*j9k<%$V{LCNYChnhQn0$BZX0Wmr2qN z?t>ZYs+y~ww%{CfDhRCWY1EiEd1_1|I$q}dYU=&SwF9$V*~d)DPhOE1ce;nl*GfqO zsf8>t!dr*CcakHh3YHGeUvQR}y$gs`e2ra11|AX)teb6ceRk%pAr2bViU)fcNO z5E$z!DRXjA`1fr+j|WbaJw_8Th?XiTRYfDJTVr^kA#<42VFh0u+LpZS95^a1!p~mr z^JQF4@Whe28eT{ET@cNHJE{gO#Y7Igcwb+_B1PLC5{snl@EfI_br^MbEXfc7U1~-? z^m_d)27C>@*|TPp8dQPptN%#+raHM{;|6QldPpG19!&BMe@Iv*yaaM zbuN7v6ilg`s-S!IIgmJr#L&JN2bp4*aU!u7ma_~9Jnel zb?wl2NAH*X(5yw+(qy=_+9$lJ%4_^B2xUWJ%b>a9#e)kjmCvP#nA_^$qa{}qLD%XE zjw-r5+66zZO4F7J&9S?5EH;29r@1Gp-eylb>h5YrYh5wOZi?yg*FE=w`RevpO;2-X zrpCHLb4=wQiyL02k|x*ty0{#9qvvqpT3BIE=%c_*sos}{K2=2R?T;@BF7P+I^SEb! z&Ju|g5G0v((4w*WYB^dFY>#hRdfXwpJHX}PDI(3*BL$`8AZPZG<@6xj9JI3dOZ$^H zo4Qu-lf{3Dwu&E~lRI2Q(UbiMbXRRPQ8Z#M_9ZG_nUf&GS`9p0Xyft7#g}SVEZ-QU zuC>hk`X%hBYpPZNMk1~J*_nyADAy5(lMmt&>T{D!jGk~>=Z3|>=CO*%!81*ti!0hg ziY*nrd-^)wgrxdepSE^!!@`=LRMxbbV~3oeYomj-X~%cV@D5 z#+J@lh0-8{ReeVV*7X>a?3V@|H)#jun!=qLCqXgV5}+1k1DQ8biTV2eDps#T+LoSs zRCtu9?l%tbii)aDcPf@MTCM-JWudmVYeztVlVscd?E_k0hSSz}E4F%qL9*-E*e|)w zos;>uMMo?uudb??5S?<-ae!GZ#EK%fNH&W^q}&CiqRXXP{nKYY)_k500PU0b4h3Wy z>Ws=toHYoli6%^tHm%+idvV3)a97VuxFT>Z)?g!Yhb3MvQP(3OvE3qb!pvdoc^V|v zDc$+28ymMGsu0S~~}?6hDDT4h>3ZgLbqu`kj(gsLwYReRlHN zyDXp<|0&t8kCv2EL9W~97N9E&R_%z+ju9Ps#h#aeeC(INi-67d2UNb21-LV!5}W_g b_S5Ni3RJD!E%$PkKUP?nTAJh;Ih_43=L}n| literal 0 HcmV?d00001 diff --git a/godot-visualizer/resources/token_cardano.png.import b/godot-visualizer/resources/token_cardano.png.import new file mode 100644 index 0000000..51e38e1 --- /dev/null +++ b/godot-visualizer/resources/token_cardano.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjuk3dem73nti" +path="res://.godot/imported/token_cardano.png-1b379e5d008802aed49301c053ac9b8a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/token_cardano.png" +dest_files=["res://.godot/imported/token_cardano.png-1b379e5d008802aed49301c053ac9b8a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/godot-visualizer/scenes/token.tscn b/godot-visualizer/scenes/token.tscn index 5d0e654..9636c98 100644 --- a/godot-visualizer/scenes/token.tscn +++ b/godot-visualizer/scenes/token.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=19 format=3 uid="uid://5y7v35p7p3lu"] +[gd_scene load_steps=20 format=3 uid="uid://5y7v35p7p3lu"] [ext_resource type="Texture2D" uid="uid://c1sms7lmfcvfe" path="res://resources/token_hosky.png" id="1_5phyg"] [ext_resource type="Script" uid="uid://bw2uwnf5pgc7t" path="res://scripts/token.gd" id="1_xdvoi"] [ext_resource type="Texture2D" uid="uid://c5xtkfexbw526" path="res://resources/token_about-stuff.png" id="2_o6iwt"] [ext_resource type="Texture2D" uid="uid://bg1knpyijgeud" path="res://resources/token_collecting-the-simpsons.png" id="3_42ep8"] +[ext_resource type="Texture2D" uid="uid://bjuk3dem73nti" path="res://resources/token_cardano.png" id="3_gr5ct"] [ext_resource type="Texture2D" uid="uid://c7b1phdcwbd7e" path="res://resources/token_pellet.png" id="3_ijof8"] [ext_resource type="Texture2D" uid="uid://dglhithj5wp1p" path="res://resources/token_stuff.png" id="3_smo0k"] [ext_resource type="Texture2D" uid="uid://dw2dk2yopxhsy" path="res://resources/token_fluid.png" id="4_gr5ct"] @@ -32,6 +33,14 @@ animations = [{ }, { "frames": [{ "duration": 1.0, +"texture": ExtResource("3_gr5ct") +}], +"loop": true, +"name": &"cardano", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, "texture": ExtResource("3_42ep8") }], "loop": true, @@ -153,7 +162,7 @@ shape = SubResource("RectangleShape2D_5phyg") [node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."] sprite_frames = SubResource("SpriteFrames_5i2ku") -animation = &"about-stuff" +animation = &"cardano" [connection signal="mouse_entered" from="Area2D" to="." method="_on_mouse_entered"] [connection signal="mouse_exited" from="Area2D" to="." method="_on_mouse_exited"] From 4e15c8666c88817319dfdfa06bb612509406c1f8 Mon Sep 17 00:00:00 2001 From: EehMauro Date: Fri, 31 Oct 2025 14:58:55 -0300 Subject: [PATCH 2/2] feat: updated how to play section --- docker/Dockerfile.frontend | 2 +- frontend/src/components/how-to-play/Menu.tsx | 6 +- .../how-to-play/create-ship/Description.mdx | 28 +- .../how-to-play/create-ship/Section.tsx | 214 ++++++---- .../how-to-play/gather-fuel/Description.mdx | 16 + .../how-to-play/gather-fuel/Section.tsx | 295 ++++++++++++++ .../how-to-play/gather-token/Description.mdx | 16 + .../how-to-play/gather-token/Section.tsx | 341 ++++++++++++++++ .../how-to-play/mine-asteria/Description.mdx | 15 + .../how-to-play/mine-asteria/Section.tsx | 309 +++++++++++++++ .../how-to-play/move-ship/Description.mdx | 12 +- .../how-to-play/move-ship/Section.tsx | 254 +++++++----- .../how-to-play/quit-game/Description.mdx | 10 + .../how-to-play/quit-game/Section.tsx | 277 +++++++++++++ frontend/src/components/ui/Input.tsx | 27 +- frontend/src/components/ui/NavBar.tsx | 5 +- frontend/src/pages/api/asteria/create-ship.ts | 3 +- frontend/src/pages/api/asteria/gather-fuel.ts | 75 ++++ .../src/pages/api/asteria/gather-token.ts | 87 ++++ .../src/pages/api/asteria/mine-asteria.ts | 78 ++++ frontend/src/pages/api/asteria/move-ship.ts | 35 +- frontend/src/pages/api/asteria/quit-game.ts | 72 ++++ frontend/src/pages/how-to-play/index.tsx | 20 + frontend/src/pages/index.tsx | 8 +- frontend/tx3/bindings/protocol.ts | 85 +++- frontend/tx3/main.tx3 | 371 +++++++++++++++--- godot-visualizer/project.godot | 2 +- godot-visualizer/scripts/map.gd | 5 + 28 files changed, 2391 insertions(+), 277 deletions(-) create mode 100644 frontend/src/components/how-to-play/gather-fuel/Description.mdx create mode 100644 frontend/src/components/how-to-play/gather-fuel/Section.tsx create mode 100644 frontend/src/components/how-to-play/gather-token/Description.mdx create mode 100644 frontend/src/components/how-to-play/gather-token/Section.tsx create mode 100644 frontend/src/components/how-to-play/mine-asteria/Description.mdx create mode 100644 frontend/src/components/how-to-play/mine-asteria/Section.tsx create mode 100644 frontend/src/components/how-to-play/quit-game/Description.mdx create mode 100644 frontend/src/components/how-to-play/quit-game/Section.tsx create mode 100644 frontend/src/pages/api/asteria/gather-fuel.ts create mode 100644 frontend/src/pages/api/asteria/gather-token.ts create mode 100644 frontend/src/pages/api/asteria/mine-asteria.ts create mode 100644 frontend/src/pages/api/asteria/quit-game.ts diff --git a/docker/Dockerfile.frontend b/docker/Dockerfile.frontend index 1a8a227..d2075f7 100644 --- a/docker/Dockerfile.frontend +++ b/docker/Dockerfile.frontend @@ -1,6 +1,6 @@ FROM --platform=linux/amd64 ubuntu:20.04 AS build -ARG GODOT_VERSION="4.4" +ARG GODOT_VERSION="4.5.1" USER root diff --git a/frontend/src/components/how-to-play/Menu.tsx b/frontend/src/components/how-to-play/Menu.tsx index 186c37e..11f9f81 100644 --- a/frontend/src/components/how-to-play/Menu.tsx +++ b/frontend/src/components/how-to-play/Menu.tsx @@ -45,10 +45,10 @@ export default function Menu({ className }: MenuProps) {
    - {/* - + + - */} +
); } \ No newline at end of file diff --git a/frontend/src/components/how-to-play/create-ship/Description.mdx b/frontend/src/components/how-to-play/create-ship/Description.mdx index fa9d111..372c59d 100644 --- a/frontend/src/components/how-to-play/create-ship/Description.mdx +++ b/frontend/src/components/how-to-play/create-ship/Description.mdx @@ -1,6 +1,32 @@ import Layout from "../mdx/Layout"; export default ({ children }) => {children}; -Creates a `ShipState` UTxO locking min ada and a `ShipToken` (minted in this tx), specifying in the datum the initial `pos_x` and `pos_y` coordinates of the ship, and setting `fuel` to an initial amount. Also adds to the `AsteriaUTxO` value the `SHIP_MINT_FEE` paid by the user. +#### This is the first step in order to play the game. + +This transaction creates a `ShipState` UTxO locking min ada, a `ShipToken` (minted in this tx) and an `INITIAL_FUEL` amount of fuel tokens (minted in this tx), specifying in the datum the initial `pos_x` and `pos_y` coordinates of the ship, the ship and pilot token names and the `last_move_latest_time` as the upper bound of the transaction's validity range (posix time). Also adds to the `AsteriaUTxO` value the `SHIP_MINT_LOVELACE_FEE` paid by the user. + +#### Rules to take into account: + +- the ship number has to be exactly +1 of the last ship minted +- the tip slot must be recent because it's used during validation of the tx +- the start position has to be at a certain distance ([manhattan distance](https://en.wikipedia.org/wiki/Taxicab_geometry)) of coordinates (0,0). For the mainnet challenge the minimum distance is `50`. + +#### You can query the next available ship and pilot token names using the following curl: + +``` +curl --location 'https://8000-ethereal-audience-bb83g6.us1.demeter.run/graphql' \ +--header 'Content-Type: application/json' \ +--data '{"query":"query { nextShipTokenName(spacetimePolicyId: \"0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43\", spacetimeAddress: \"addr1wypfrtn6awhsvjmc24pqj0ptzvtfalang33rq8ng6j6y7scnlkytx\") { shipName pilotName } }","variables":{}}' +``` + +#### You can query the tip of the chain using the following curl: + +``` +curl --location 'https://8000-ethereal-audience-bb83g6.us1.demeter.run/graphql' \ +--header 'Content-Type: application/json' \ +--data '{"query":"query { lastSlot { slot } }","variables":{}}' +``` + +#### Diagram ![createShip diagram](/txs/build-ship.png) \ No newline at end of file diff --git a/frontend/src/components/how-to-play/create-ship/Section.tsx b/frontend/src/components/how-to-play/create-ship/Section.tsx index fe6fcb1..10f0028 100644 --- a/frontend/src/components/how-to-play/create-ship/Section.tsx +++ b/frontend/src/components/how-to-play/create-ship/Section.tsx @@ -15,93 +15,116 @@ import type { ConnectedWallet } from '@/hooks/useCardano'; import { useChallengeStore } from '@/stores/challenge'; const tx3File = `tx create_ship( - p_pos_x: Int, // Ship Position X - p_pos_y: Int, // Ship Position Y - ship_name: Bytes, // Name of the ship - pilot_name: Bytes, // Name of the pilot - tip_slot: Int, // TODO: remove when tip_slot() implemented + p_pos_x: Int, // Ship Position X + p_pos_y: Int, // Ship Position Y + ship_name: Bytes, // Name of the ship + pilot_name: Bytes, // Name of the pilot + tip_slot: Int, // TODO: remove when tip_slot() implemented + last_move_timestamp: Int, ) { - locals { - initial_fuel: 480, // Should be taken from spaceTime datum - ship_mint_lovelace_fee: 1000000, // Should be taken from asteria script datum - spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, - spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, - asteria_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#0, - pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, - } + locals { + initial_fuel: 5, // Should be taken from spaceTime datum + ship_mint_lovelace_fee: 1000000, // Should be taken from asteria script datum + spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, + spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, + asteria_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#0, + pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, + } - validity { - until_slot: tip_slot, // tip_slot() + 300 - } + validity { + until_slot: tip_slot, // tip_slot() + 300 + } - reference SpacetimeRef { - ref: spacetime_policy_ref, - } + reference SpacetimeRef { + ref: spacetime_policy_ref, + } - reference AsteriaRef { - ref: asteria_policy_ref, - } + reference AsteriaRef { + ref: asteria_policy_ref, + } - reference PelletRef { - ref: pellet_policy_ref, - } + reference PelletRef { + ref: pellet_policy_ref, + } - input source { - from: Player, - min_amount: fees + Ada(ship_mint_lovelace_fee), - } + input* gas { + from: Player, + min_amount: fees + Ada(ship_mint_lovelace_fee) + min_utxo(pilot_token) + min_utxo(new_ship) + min_utxo(gas_change), + } - input asteria { - from: AsteriaPolicy, - min_amount: AdminToken(1), - datum_is: AsteriaDatum, - } - - mint { - amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + AnyAsset(spacetime_policy_hash, ship_name, 1), - redeemer: (), - } + input asteria { + from: AsteriaPolicy, + min_amount: AdminToken(1), + datum_is: AsteriaDatum, + redeemer: AsteriaRedeemer::AddNewShip {}, + } + + mint { + amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipyardRedeemer::MintShip {}, + } - mint { - amount: Fuel(initial_fuel), - redeemer: (), - } + mint { + amount: Fuel(initial_fuel), + redeemer: FuelRedeemer::MintFuel {}, + } - output { - to: Player, - amount: source - fees - Ada(ship_mint_lovelace_fee) + AnyAsset(spacetime_policy_hash, pilot_name, 1), - } + output pilot_token { + to: Player, + amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + min_utxo(pilot_token), + } - output { - to: AsteriaPolicy, - amount: asteria + Ada(ship_mint_lovelace_fee), - datum: AsteriaDatum { - ship_counter: asteria.ship_counter + 1, - shipyard_policy: asteria.shipyard_policy, - }, - } + output updated_asteria { + to: AsteriaPolicy, + amount: asteria + Ada(ship_mint_lovelace_fee), + datum: AsteriaDatum { + ship_counter: asteria.ship_counter + 1, + shipyard_policy: asteria.shipyard_policy, + }, + } - output { - to: SpacetimePolicy, - amount: AnyAsset(spacetime_policy_hash, ship_name, 1) + Fuel(initial_fuel), - datum: ShipDatum { - pos_x: p_pos_x, - pos_y: p_pos_y, - ship_token_name: ship_name, - pilot_token_name: pilot_name, - last_move_latest_time: tip_slot, - }, - } + output new_ship { + to: SpacetimePolicy, + amount: AnyAsset(spacetime_policy_hash, ship_name, 1) + Fuel(initial_fuel) + min_utxo(new_ship), + datum: ShipDatum { + pos_x: p_pos_x, + pos_y: p_pos_y, + ship_token_name: ship_name, + pilot_token_name: pilot_name, + last_move_latest_time: last_move_timestamp, + }, + } + + output gas_change { + to: Player, + amount: gas - fees - Ada(ship_mint_lovelace_fee) - min_utxo(pilot_token) - min_utxo(new_ship), + } + + collateral { + from: Player, + min_amount: fees, + } }`; -const jsFile = `const result = await protocol.createShipTx({ +const jsFile = `const playerAddress = process.env.PLAYER_ADDRESS; +const positionX = 4; // Replace with your desired start X position +const positionY = -50; // Replace with your desired start Y position +const shipName = "SHIP0"; // Replace 0 with the next ship number +const pilotName = "PILOT0"; // Replace 0 with the next ship number +const tipSlot = 165246231; // Replace with the latest block slot +const lastMoveTimestamp = Date.now() + 300_000; + +const args: CreateShipParams = { player: playerAddress, pPosX: positionX, pPosY: positionY, - pilotName: new TextEncoder().encode(\`PILOT\${shipNumber}\`), - shipName: new TextEncoder().encode(\`SHIP\${shipNumber}\`), - tipSlot: blockSlotValue + 300, // 5 minutes from last block -});`; + pilotName: new TextEncoder().encode(pilotName), + shipName: new TextEncoder().encode(shipName), + tipSlot: tipSlot + 300, // 5 minutes from last block + lastMoveTimestamp, +}; + +const response = await client.createShipTx(args);`; interface CreateShipProps { isActive: boolean; @@ -116,6 +139,8 @@ export default function CreateShip(props: CreateShipProps) { const [position, setPosition] = useState<{ x: number; y: number }|null>(null); const [wallet, setWallet] = useState(null); const [address, setAddress] = useState(''); + const [shipNumber, setShipNumber] = useState(undefined); + const [tipSlot, setTipSlot] = useState(undefined); const errors = formState.errors || {}; const dataTx = formState.data?.tx; @@ -155,6 +180,36 @@ export default function CreateShip(props: CreateShipProps) { setAddress(connectedWallet?.changeAddress || ''); } + const handleFetchShipNumber = async () => { + const shipRes = await (await fetch("https://8000-ethereal-audience-bb83g6.us1.demeter.run/graphql", { + "headers": { "content-type": "application/json" }, + "body": "{\"query\":\"query { nextShipTokenName(spacetimePolicyId: \\\"0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43\\\", spacetimeAddress: \\\"addr1wypfrtn6awhsvjmc24pqj0ptzvtfalang33rq8ng6j6y7scnlkytx\\\") { shipName pilotName } }\",\"variables\":{}}", + "method": "POST" + })).json(); + + console.log(shipRes); + + setShipNumber(parseInt(shipRes.data.nextShipTokenName.shipName.replace('SHIP', ''))); + } + + const updateShipNumber = (event: ChangeEvent) => { + setShipNumber(parseInt(event.target.value)); + } + + const handleFetchLastSlot = async () => { + const slotRes = await (await fetch("https://8000-ethereal-audience-bb83g6.us1.demeter.run/graphql", { + "headers": { "content-type": "application/json" }, + "body": "{\"query\":\"query { lastSlot { slot } }\"}", + "method": "POST" + })).json(); + + setTipSlot(slotRes.data.lastSlot.slot); + } + + const updateTipSlot = (event: ChangeEvent) => { + setTipSlot(parseInt(event.target.value)); + } + const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); setSubmitting(true); @@ -179,9 +234,9 @@ export default function CreateShip(props: CreateShipProps) { return ( - {/* + - */} +
@@ -199,7 +254,7 @@ export default function CreateShip(props: CreateShipProps) { -
+
+
diff --git a/frontend/src/components/how-to-play/gather-fuel/Description.mdx b/frontend/src/components/how-to-play/gather-fuel/Description.mdx new file mode 100644 index 0000000..2c7eb3d --- /dev/null +++ b/frontend/src/components/how-to-play/gather-fuel/Description.mdx @@ -0,0 +1,16 @@ +import Layout from "../mdx/Layout"; +export default ({ children }) => {children}; + +#### With this transaction you can gather fuel from the fuel pellets for your ship. + +Updates the amount of fuel tokens in both `ShipState` and `PelletState` UTxOs, adding the `amount` (specified in the redeemer) from the first and subtracting it from the latter. Also allows the ship owner to get any amount of the prize tokens held in the pellet. + +#### Rules to take into account: + +- the ship number has to be the one controlled by the pilot token in your wallet. +- the ship has to be in the same position than the pellet +- the ship can't have more than 5 fuel units + +#### Diagram + +![gatherFuel diagram](/txs/gather-fuel.png) \ No newline at end of file diff --git a/frontend/src/components/how-to-play/gather-fuel/Section.tsx b/frontend/src/components/how-to-play/gather-fuel/Section.tsx new file mode 100644 index 0000000..060b505 --- /dev/null +++ b/frontend/src/components/how-to-play/gather-fuel/Section.tsx @@ -0,0 +1,295 @@ +import { ChangeEvent, useEffect, useState } from 'react'; +import { usePathname } from 'next/navigation'; + +import Input from '@/components/ui/Input'; +import Alert from '@/components/ui/Alert'; +import CodeBlock from '@/components/ui/CodeBlock'; +import CopyButton from '@/components/ui/CopyButton'; +import ConnectWallet from '@/components/ui/ConnectWallet'; + +import Tabs, { Tab } from '@/components/how-to-play/Tabs'; +import GatherFuelDescription from '@/components/how-to-play/gather-fuel/Description.mdx'; + +import type { ResponseData } from '@/pages/api/asteria/gather-fuel'; +import type { ConnectedWallet } from '@/hooks/useCardano'; +import { useChallengeStore } from '@/stores/challenge'; + +const tx3File = `tx gather_fuel( + p_amount: Int, + ship_name: Bytes, + pilot_name: Bytes, + pellet_ref: UtxoRef, + since_slot: Int, +) { + locals { + spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, + spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, + pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, + } + + validity { + since_slot: since_slot, + } + + reference SpacetimeRef { + ref: spacetime_policy_ref, + } + + reference PelletRef { + ref: pellet_policy_ref, + } + + input pellet { + ref: pellet_ref, + redeemer: PelletRedeemer::Provide { + amount: p_amount, + }, + } + + input ship { + from: SpacetimePolicy, + datum_is: ShipDatum, + min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipRedeemer::GatherFuel { + amount: p_amount, + }, + } + + input* pilot { + from: Player, + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), + } + + output { + to: PelletPolicy, + amount: pellet - Fuel(p_amount), + datum: PelletDatum {...pellet}, + } + + output { + to: SpacetimePolicy, + amount: ship + Fuel(p_amount), + datum: ShipDatum {...ship}, + } + + output pilot_change { + to: Player, + amount: pilot - fees, + } + + collateral { + from: Player, + min_amount: fees, + } + +}`; + +const jsFile = `const fuelAmount = 4; // Replace with the desired fuel to gather +const shipName = "SHIP0"; // Replace with your ship name +const pilotName = "PILOT0"; // Replace with your pilot name +const pelletRef = "acbb34e95c67bc4432557088138dbcc00d957756ebac6480e2499d1088b0b7be#13"; // Pellet UTxO reference + +const playerAddress = process.env.PLAYER_ADDRESS; +const tipSlot = 165453127; // Replace with the latest block slot + +const sinceSlot = tipSlot - 100; // 100 is just to be safe + +const args: GatherFuelParams = { + player: playerAddress, + pAmount: fuelAmount, + pilotName: new TextEncoder().encode(pilotName), + shipName: new TextEncoder().encode(shipName), + pelletRef, + sinceSlot, +}; + +const response = await client.gatherFuelTx(args);`; + +type ActionState = { + data?: { + tx?: string; + }; + errors?: Record; +}; + +interface GatherFuelProps { + isActive: boolean; +} + +export default function GatherFuel(props: GatherFuelProps) { + const { current } = useChallengeStore(); + + const pathname = usePathname() || ''; + const [submitting, setSubmitting] = useState(false); + const [formState, setFormState] = useState({}); + const [wallet, setWallet] = useState(null); + const [address, setAddress] = useState(''); + const [shipNumber, setShipNumber] = useState(''); + const [tipSlot, setTipSlot] = useState(undefined); + + const errors = formState.errors || {}; + const dataTx = formState.data?.tx; + + useEffect(() => { + if (props.isActive) { + window.GODOT_BRIDGE?.send({ action: 'clear_placeholder' }); + } + }, [props.isActive]); + + const updateShipNumber = (event: ChangeEvent) => { + setShipNumber(event.target.value); + window.GODOT_BRIDGE?.send({ action: 'select_ship', shipNumber: event.target.value }); + } + + const handleFetchLastSlot = async () => { + const slotRes = await (await fetch("https://8000-ethereal-audience-bb83g6.us1.demeter.run/graphql", { + "headers": { "content-type": "application/json" }, + "body": "{\"query\":\"query { lastSlot { slot } }\"}", + "method": "POST" + })).json(); + + setTipSlot(slotRes.data.lastSlot.slot); + } + + const updateTipSlot = (event: ChangeEvent) => { + setTipSlot(parseInt(event.target.value)); + } + + const handleWallet = (connectedWallet: ConnectedWallet|null) => { + setWallet(connectedWallet); + setAddress(connectedWallet?.changeAddress || ''); + } + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setSubmitting(true); + setFormState({}); + const formData = new FormData(event.currentTarget); + const data = Object.fromEntries(formData.entries()); + data['network'] = current().network; + try { + const res = await fetch('/api/asteria/gather-fuel', { + method: 'POST', + body: JSON.stringify(data), + headers: { 'Content-Type': 'application/json' }, + }); + const jsonResponse = await res.json(); + setFormState(jsonResponse); + } catch (error) { + console.log('Error submitting form:', error); + } finally { + setSubmitting(false); + } + }; + + return ( + + + + + + + +
+ setAddress(e.target.value)} + required + /> + + + +
+
+ +
+ +
+ +
+ + +
+
+ + {dataTx && ( + + {dataTx} + + )} + + {errors.global && {errors.global}} + +
+ +
+ +
+ + }> +
+ +
+
+ + }> +
+ +
+
+
+ ); +} diff --git a/frontend/src/components/how-to-play/gather-token/Description.mdx b/frontend/src/components/how-to-play/gather-token/Description.mdx new file mode 100644 index 0000000..b01f58b --- /dev/null +++ b/frontend/src/components/how-to-play/gather-token/Description.mdx @@ -0,0 +1,16 @@ +import Layout from "../mdx/Layout"; +export default ({ children }) => {children}; + +#### With this transaction you can gather fuel from the fuel pellets for your ship and retrieve tokens from the pellet. + +Updates the amount of fuel tokens in both `ShipState` and `PelletState` UTxOs, adding the `amount` (specified in the redeemer) from the first and subtracting it from the latter. Also allows the ship owner to get any amount of the prize tokens held in the pellet. + +#### Rules to take into account: + +- the ship number has to be the one controlled by the pilot token in your wallet. +- the ship has to be in the same position than the pellet +- the ship can't have more than 5 fuel units + +#### Diagram + +![gatherFuel diagram](/txs/gather-fuel.png) \ No newline at end of file diff --git a/frontend/src/components/how-to-play/gather-token/Section.tsx b/frontend/src/components/how-to-play/gather-token/Section.tsx new file mode 100644 index 0000000..1721172 --- /dev/null +++ b/frontend/src/components/how-to-play/gather-token/Section.tsx @@ -0,0 +1,341 @@ +import { ChangeEvent, useEffect, useState } from 'react'; +import { usePathname } from 'next/navigation'; + +import Input from '@/components/ui/Input'; +import Alert from '@/components/ui/Alert'; +import CodeBlock from '@/components/ui/CodeBlock'; +import CopyButton from '@/components/ui/CopyButton'; +import ConnectWallet from '@/components/ui/ConnectWallet'; + +import Tabs, { Tab } from '@/components/how-to-play/Tabs'; +import GatherTokenDescription from '@/components/how-to-play/gather-token/Description.mdx'; + +import type { ResponseData } from '@/pages/api/asteria/gather-token'; +import type { ConnectedWallet } from '@/hooks/useCardano'; +import { useChallengeStore } from '@/stores/challenge'; + +const tx3File = `tx gather_token( + ship_name: Bytes, + pilot_name: Bytes, + pellet_ref: UtxoRef, + fuel_amount: Int, + token_name: Bytes, + token_amount: Int, + token_policy_hash: Bytes, + since_slot: Int, +) { + locals { + spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, + spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, + pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, + } + + validity { + since_slot: since_slot, + } + + reference SpacetimeRef { + ref: spacetime_policy_ref, + } + + reference PelletRef { + ref: pellet_policy_ref, + } + + input pellet { + ref: pellet_ref, + redeemer: PelletRedeemer::Provide { + amount: fuel_amount, + }, + } + + input ship { + from: SpacetimePolicy, + datum_is: ShipDatum, + min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipRedeemer::GatherFuel { + amount: fuel_amount, + }, + } + + input* pilot { + from: Player, + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), + } + + output { + to: PelletPolicy, + amount: pellet - Fuel(fuel_amount) - AnyAsset(token_policy_hash, token_name, token_amount), + datum: PelletDatum {...pellet}, + } + + output { + to: SpacetimePolicy, + amount: ship + Fuel(fuel_amount), + datum: ShipDatum {...ship}, + } + + output pilot_change { + to: Player, + amount: pilot - fees + AnyAsset(token_policy_hash, token_name, token_amount), + } + + collateral { + from: Player, + min_amount: fees, + } + +}`; + +const jsFile = `const fuelAmount = 3; // Replace with the desired fuel to gather +const shipName = "SHIP0"; // Replace with your ship name +const pilotName = "PILOT0"; // Replace with your pilot name +const pelletRef = "2853b765da3e75237f1d6d70c0db3315d67afcc3c20812775ccf74246b383b6b#62"; // Pellet UTxO reference + +const tokenAmount = 1; // Token amount to gather +const tokenName = "AS09361"; // Token name +const tokenPolicyHash = Uint8Array.from(Buffer.from("b4df23876be7207fe26e0cddfb08d6a73ff83754075efafb53446234", "hex")); // Token policy hash + +const playerAddress = process.env.PLAYER_ADDRESS; +const tipSlot = 165470021; // Replace with the latest block slot + +const sinceSlot = tipSlot - 100; // 100 is just to be safe + +const args: GatherTokenParams = { + player: playerAddress, + pilotName: new TextEncoder().encode(pilotName), + shipName: new TextEncoder().encode(shipName), + pelletRef, + fuelAmount: fuelAmount, + tokenAmount, + tokenName: new TextEncoder().encode(tokenName), + tokenPolicyHash, + sinceSlot, +}; + +const response = await client.gatherTokenTx(args);`; + +type ActionState = { + data?: { + tx?: string; + }; + errors?: Record; +}; + +interface GatherTokenProps { + isActive: boolean; +} + +export default function GatherToken(props: GatherTokenProps) { + const { current } = useChallengeStore(); + + const pathname = usePathname() || ''; + const [submitting, setSubmitting] = useState(false); + const [formState, setFormState] = useState({}); + const [wallet, setWallet] = useState(null); + const [address, setAddress] = useState(''); + const [shipNumber, setShipNumber] = useState(''); + const [tipSlot, setTipSlot] = useState(undefined); + + const errors = formState.errors || {}; + const dataTx = formState.data?.tx; + + useEffect(() => { + if (props.isActive) { + window.GODOT_BRIDGE?.send({ action: 'clear_placeholder' }); + } + }, [props.isActive]); + + const updateShipNumber = (event: ChangeEvent) => { + setShipNumber(event.target.value); + window.GODOT_BRIDGE?.send({ action: 'select_ship', shipNumber: event.target.value }); + } + + const handleFetchLastSlot = async () => { + const slotRes = await (await fetch("https://8000-ethereal-audience-bb83g6.us1.demeter.run/graphql", { + "headers": { "content-type": "application/json" }, + "body": "{\"query\":\"query { lastSlot { slot } }\"}", + "method": "POST" + })).json(); + + setTipSlot(slotRes.data.lastSlot.slot); + } + + const updateTipSlot = (event: ChangeEvent) => { + setTipSlot(parseInt(event.target.value)); + } + + const handleWallet = (connectedWallet: ConnectedWallet|null) => { + setWallet(connectedWallet); + setAddress(connectedWallet?.changeAddress || ''); + } + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setSubmitting(true); + setFormState({}); + const formData = new FormData(event.currentTarget); + const data = Object.fromEntries(formData.entries()); + data['network'] = current().network; + try { + const res = await fetch('/api/asteria/gather-token', { + method: 'POST', + body: JSON.stringify(data), + headers: { 'Content-Type': 'application/json' }, + }); + const jsonResponse = await res.json(); + setFormState(jsonResponse); + } catch (error) { + console.log('Error submitting form:', error); + } finally { + setSubmitting(false); + } + }; + + return ( + + + + + + +
+
+ setAddress(e.target.value)} + required + /> + + + +
+
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + {dataTx && ( + + {dataTx} + + )} + + {errors.global && {errors.global}} + +
+ +
+
+
+ + }> +
+ +
+
+ + }> +
+ +
+
+
+ ); +} diff --git a/frontend/src/components/how-to-play/mine-asteria/Description.mdx b/frontend/src/components/how-to-play/mine-asteria/Description.mdx new file mode 100644 index 0000000..21b94c2 --- /dev/null +++ b/frontend/src/components/how-to-play/mine-asteria/Description.mdx @@ -0,0 +1,15 @@ +import Layout from "../mdx/Layout"; +export default ({ children }) => {children}; + +#### With this transaction you can retrieve the asteria prize. + +Subtracts from the `AsteriaUTxO` at most `MAX_ASTERIA_MINING`% of the ada value, and pays that amount to the owner of the ship that reached Asteria, together with the min ada locked in the `ShipState` UTxO. The `ShipToken` and the remaining fuel tokens are burnt. + +#### Rules to take into account: + +- the ship number has to be the one controlled by the pilot token in your wallet. +- the ship has to be in the position (0,0) + +#### Diagram + +![mineAsteria diagram](/txs/mine-asteria.png) \ No newline at end of file diff --git a/frontend/src/components/how-to-play/mine-asteria/Section.tsx b/frontend/src/components/how-to-play/mine-asteria/Section.tsx new file mode 100644 index 0000000..a4b0d81 --- /dev/null +++ b/frontend/src/components/how-to-play/mine-asteria/Section.tsx @@ -0,0 +1,309 @@ +import { ChangeEvent, useEffect, useState } from 'react'; +import { usePathname } from 'next/navigation'; + +import Input from '@/components/ui/Input'; +import Alert from '@/components/ui/Alert'; +import CodeBlock from '@/components/ui/CodeBlock'; +import CopyButton from '@/components/ui/CopyButton'; +import ConnectWallet from '@/components/ui/ConnectWallet'; + +import Tabs, { Tab } from '@/components/how-to-play/Tabs'; +import MineAsteriaDescription from '@/components/how-to-play/mine-asteria/Description.mdx'; + +import type { ResponseData } from '@/pages/api/asteria/mine-asteria'; +import type { ConnectedWallet } from '@/hooks/useCardano'; +import { useChallengeStore } from '@/stores/challenge'; + +const tx3File = `tx mine_asteria( + ship_name: Bytes, + pilot_name: Bytes, + ship_fuel: Int, + mine_amount: Int, + since_slot: Int, +) { + locals { + spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, + spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, + asteria_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#0, + pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, + } + + validity { + since_slot: since_slot, + } + + reference SpacetimeRef { + ref: spacetime_policy_ref, + } + + reference AsteriaRef { + ref: asteria_policy_ref, + } + + reference PelletRef { + ref: pellet_policy_ref, + } + + input asteria { + from: AsteriaPolicy, + min_amount: AdminToken(1), + datum_is: AsteriaDatum, + redeemer: AsteriaRedeemer::Mine {}, + } + + input ship { + from: SpacetimePolicy, + datum_is: ShipDatum, + min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipRedeemer::MineAsteria {}, + } + + input* pilot { + from: Player, + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), + } + + mint { + amount: Fuel(ship_fuel) - Fuel(ship_fuel) - Fuel(ship_fuel), + redeemer: FuelRedeemer::BurnFuel {}, + } + + mint { + amount: AnyAsset(spacetime_policy_hash, ship_name, 1) - AnyAsset(spacetime_policy_hash, ship_name, 1) - AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipyardRedeemer::BurnShip {}, + } + + output { + to: AsteriaPolicy, + amount: asteria - Ada(mine_amount), + datum: AsteriaDatum {...asteria}, + } + + output { + to: SpacetimePolicy, + amount: ship - AnyAsset(spacetime_policy_hash, ship_name, 1) - Fuel(ship_fuel), + datum: ShipDatum {...ship}, + } + + output pilot_change { + to: Player, + amount: pilot - fees + Ada(mine_amount), + } + + collateral { + from: Player, + min_amount: fees, + } + +}`; + +const jsFile = `const shipName = "SHIP0"; // Replace with your ship name +const pilotName = "PILOT0"; // Replace with your pilot name +const shipFuel = 3; // Replace with the ship fuel +const mineAmount = 11_500_000; // Replace with the mine amount -- it should be 50% of asteria ada + +const playerAddress = process.env.PLAYER_ADDRESS; +const tipSlot = 165470021; // Replace with the latest block slot + +const sinceSlot = tipSlot - 100; // 100 is just to be safe + +const args: MineAsteriaParams = { + player: playerAddress, + pilotName: new TextEncoder().encode(pilotName), + shipName: new TextEncoder().encode(shipName), + shipFuel, + mineAmount, + sinceSlot, +}; + +const response = await client.mineAsteriaTx(args);`; + +type ActionState = { + data?: { + tx?: string; + }; + errors?: Record; +}; + +interface MineAsteriaProps { + isActive: boolean; +} + +export default function MineAsteria(props: MineAsteriaProps) { + const { current } = useChallengeStore(); + + const pathname = usePathname() || ''; + const [submitting, setSubmitting] = useState(false); + const [formState, setFormState] = useState({}); + const [wallet, setWallet] = useState(null); + const [address, setAddress] = useState(''); + const [shipNumber, setShipNumber] = useState(''); + const [tipSlot, setTipSlot] = useState(undefined); + + const errors = formState.errors || {}; + const dataTx = formState.data?.tx; + + useEffect(() => { + if (props.isActive) { + window.GODOT_BRIDGE?.send({ action: 'clear_placeholder' }); + } + }, [props.isActive]); + + const updateShipNumber = (event: ChangeEvent) => { + setShipNumber(event.target.value); + window.GODOT_BRIDGE?.send({ action: 'select_ship', shipNumber: event.target.value }); + } + + const handleFetchLastSlot = async () => { + const slotRes = await (await fetch("https://8000-ethereal-audience-bb83g6.us1.demeter.run/graphql", { + "headers": { "content-type": "application/json" }, + "body": "{\"query\":\"query { lastSlot { slot } }\"}", + "method": "POST" + })).json(); + + setTipSlot(slotRes.data.lastSlot.slot); + } + + const updateTipSlot = (event: ChangeEvent) => { + setTipSlot(parseInt(event.target.value)); + } + + const handleWallet = (connectedWallet: ConnectedWallet|null) => { + setWallet(connectedWallet); + setAddress(connectedWallet?.changeAddress || ''); + } + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setSubmitting(true); + setFormState({}); + const formData = new FormData(event.currentTarget); + const data = Object.fromEntries(formData.entries()); + data['network'] = current().network; + try { + const res = await fetch('/api/asteria/mine-asteria', { + method: 'POST', + body: JSON.stringify(data), + headers: { 'Content-Type': 'application/json' }, + }); + const jsonResponse = await res.json(); + setFormState(jsonResponse); + } catch (error) { + console.log('Error submitting form:', error); + } finally { + setSubmitting(false); + } + }; + + return ( + + + + + + +
+
+ setAddress(e.target.value)} + required + /> + + + +
+
+ +
+ +
+ +
+ + + +
+
+ + {dataTx && ( + + {dataTx} + + )} + + {errors.global && {errors.global}} + +
+ +
+
+
+ + }> +
+ +
+
+ + }> +
+ +
+
+
+ ); +} diff --git a/frontend/src/components/how-to-play/move-ship/Description.mdx b/frontend/src/components/how-to-play/move-ship/Description.mdx index 47b9b6c..9e5765c 100644 --- a/frontend/src/components/how-to-play/move-ship/Description.mdx +++ b/frontend/src/components/how-to-play/move-ship/Description.mdx @@ -1,6 +1,16 @@ import Layout from "../mdx/Layout"; export default ({ children }) => {children}; -Updates the `pos_x`, `pos_y` and `fuel` datum fields of the `ShipState` UTxO by adding the `delta_x` and `delta_y` values specified in the redeemer, and subtracting the fuel amount needed for the displacement. +#### With this transaction you can move the ship through the space. + +This transaction updates the `pos_x`, `pos_y` and `fuel` datum fields of the `ShipState` UTxO by adding the `delta_x` and `delta_y` values specified in the redeemer, and subtracts from the ship value the amount of fuel tokens needed for the displacement (which are burnt in this tx). Also updates the `last_move_latest_time` field with the transaction's validity range latest posix time. + +#### Rules to take into account: + +- the ship number has to be the one controlled by the pilot token in your wallet. +- the total distance you travel is constrained by the number of slots between your tx validity range (ttl end - ttl start). +- the amount of fuel you pass is proportional to the total distance you travel (deltaX + deltaY). + +#### Diagram ![moveShip diagram](/txs/move-ship.png) \ No newline at end of file diff --git a/frontend/src/components/how-to-play/move-ship/Section.tsx b/frontend/src/components/how-to-play/move-ship/Section.tsx index 66b5441..50232d2 100644 --- a/frontend/src/components/how-to-play/move-ship/Section.tsx +++ b/frontend/src/components/how-to-play/move-ship/Section.tsx @@ -15,84 +15,105 @@ import type { ConnectedWallet } from '@/hooks/useCardano'; import { useChallengeStore } from '@/stores/challenge'; const tx3File = `tx move_ship( - p_delta_x: Int, - p_delta_y: Int, - ship_name: Bytes, - pilot_name: Bytes, - required_fuel: Int, - tip_slot: Int, // TODO: remove when tip_slot() implemented + p_delta_x: Int, + p_delta_y: Int, + ship_name: Bytes, + pilot_name: Bytes, + required_fuel: Int, + since_slot: Int, + until_slot: Int, + last_move_timestamp: Int, ) { - locals { - spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, - spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, - pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, - } + locals { + spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, + spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, + pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, + } - validity { - until_slot: tip_slot, // tip_slot() + 300 - } + validity { + since_slot: since_slot, + until_slot: until_slot, + } - reference SpacetimeRef { - ref: spacetime_policy_ref, - } + reference SpacetimeRef { + ref: spacetime_policy_ref, + } - reference PelletRef { - ref: pellet_policy_ref, - } + reference PelletRef { + ref: pellet_policy_ref, + } - input source { - from: Player, - min_amount: fees, - } + collateral { + from: Player, + min_amount: fees, + } - input ship { - from: SpacetimePolicy, - datum_is: ShipDatum, - min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), - redeemer: ShipActions::MoveShip { - delta_x: p_delta_x, - delta_y: p_delta_y, - }, - } + input ship { + from: SpacetimePolicy, + datum_is: ShipDatum, + min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipRedeemer::MoveShip { + delta_x: p_delta_x, + delta_y: p_delta_y, + }, + } - input pilot { - from: Player, - min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1), - } + input* pilot { + from: Player, + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), + } - burn { - amount: Fuel(required_fuel), - redeemer: (), - } + mint { + amount: Fuel(required_fuel) - Fuel(required_fuel) - Fuel(required_fuel), + redeemer: FuelRedeemer::BurnFuel {}, + } - output { - to: SpacetimePolicy, - amount: ship - Fuel(required_fuel), - datum: ShipDatum { - pos_x: ship.pos_x + p_delta_x, - pos_y: ship.pos_y + p_delta_y, - last_move_latest_time: tip_slot, - ...ship - }, - } + output { + to: SpacetimePolicy, + amount: ship - Fuel(required_fuel), + datum: ShipDatum { + pos_x: ship.pos_x + p_delta_x, + pos_y: ship.pos_y + p_delta_y, + last_move_latest_time: last_move_timestamp, + ...ship + }, + } - output { - to: Player, - amount: source - fees + pilot, - } -}`; + output pilot_change { + to: Player, + amount: pilot - fees, + } -const jsFile = `const distance = Math.abs(positionX) + Math.abs(positionY); +}`; -const result = await protocol.moveShipTx({ +const jsFile = `const slotRequiredPerStep = 12096; +const playerAddress = process.env.PLAYER_ADDRESS; +const deltaX = 0; // Replace with your desired X movement units +const deltaY = 5; // Replace with your desired Y movement units +const requiredFuel = 5; // Replace with the required fuel for the movement +const shipName = "SHIP0"; // Replace with your ship name +const pilotName = "PILOT0"; // Replace with your pilot name +const tipSlot = 165246300; // Replace with the latest block slot + +// computed values +const distance = Math.abs(deltaX) + Math.abs(deltaY); +const sinceSlot = tipSlot - 100; // 100 is just to be safe; +const untilSlot = tipSlot + slotRequiredPerStep * distance; +const lastMoveTimestamp = Date.now() + slotRequiredPerStep * distance * 1000; + +const args: MoveShipParams = { player: playerAddress, - pDeltaX: positionX, - pDeltaY: positionY, - shipName: new TextEncoder().encode(\`SHIP\${shipNumber}\`), - pilotName: new TextEncoder().encode(\`PILOT\${shipNumber}\`), - requiredFuel: distance * 60, // fuel_per_step from SpaceTime datum - tip_slot: blockSlotValue + 300, // 5 minutes from last block -});`; + pDeltaX: deltaX, + pDeltaY: deltaY, + requiredFuel: requiredFuel, + pilotName: new TextEncoder().encode(pilotName), + shipName: new TextEncoder().encode(shipName), + sinceSlot, + untilSlot, + lastMoveTimestamp, +}; + +const response = await client.moveShipTx(args);`; type ActionState = { data?: { @@ -112,9 +133,11 @@ export default function MoveShip(props: MoveShipProps) { const [submitting, setSubmitting] = useState(false); const [formState, setFormState] = useState({}); const [position, setPosition] = useState<{ x: number; y: number }|null>(null); + const [delta, setDelta] = useState<{ x: number; y: number }|null>(null); const [wallet, setWallet] = useState(null); const [address, setAddress] = useState(''); const [shipNumber, setShipNumber] = useState(''); + const [tipSlot, setTipSlot] = useState(undefined); const errors = formState.errors || {}; const dataTx = formState.data?.tx; @@ -128,26 +151,32 @@ export default function MoveShip(props: MoveShipProps) { useEffect(() => { window.addEventListener('message', (event) => { if (pathname.includes('how-to-play') && window.location.hash === '#move-ship') { - if (event.data.action == 'map_click') { + if (event.data.action == 'ship_selected') { updatePosition(event.data.position.x, event.data.position.y); } } }); }, []); + + const updatePosition = (x: number, y: number) => { + setPosition({ x, y }); + } - const updatePositionX = (event: ChangeEvent) => { - updatePosition(parseInt(event.target.value), position ? position.y : 0); + const updateDeltaX = (event: ChangeEvent) => { + updateDelta(parseInt(event.target.value), delta ? delta.y : 0); } - const updatePositionY = (event: ChangeEvent) => { - updatePosition(position ? position.x : 0, parseInt(event.target.value)); + const updateDeltaY = (event: ChangeEvent) => { + updateDelta(delta ? delta.x : 0, parseInt(event.target.value)); } - - const updatePosition = (x: number, y: number) => { - setPosition({ x, y }); - console.log({ x, y, shipNumber }); - window.GODOT_BRIDGE?.send({ action: 'select_ship', shipNumber }); - window.GODOT_BRIDGE?.send({ action: 'move_ship', x, y }); + + const updateDelta = (x: number, y: number) => { + setDelta({ x, y }); + window.GODOT_BRIDGE?.send({ + action: 'move_ship', + x: x + (position ? position.x : 0), + y: y + (position ? position.y : 0) + }); } const updateShipNumber = (event: ChangeEvent) => { @@ -155,6 +184,20 @@ export default function MoveShip(props: MoveShipProps) { window.GODOT_BRIDGE?.send({ action: 'select_ship', shipNumber: event.target.value }); } + const handleFetchLastSlot = async () => { + const slotRes = await (await fetch("https://8000-ethereal-audience-bb83g6.us1.demeter.run/graphql", { + "headers": { "content-type": "application/json" }, + "body": "{\"query\":\"query { lastSlot { slot } }\"}", + "method": "POST" + })).json(); + + setTipSlot(slotRes.data.lastSlot.slot); + } + + const updateTipSlot = (event: ChangeEvent) => { + setTipSlot(parseInt(event.target.value)); + } + const handleWallet = (connectedWallet: ConnectedWallet|null) => { setWallet(connectedWallet); setAddress(connectedWallet?.changeAddress || ''); @@ -184,9 +227,9 @@ export default function MoveShip(props: MoveShipProps) { return ( - {/* + - */} +
@@ -205,18 +248,21 @@ export default function MoveShip(props: MoveShipProps) {
- +
+ +
diff --git a/frontend/src/components/how-to-play/quit-game/Description.mdx b/frontend/src/components/how-to-play/quit-game/Description.mdx new file mode 100644 index 0000000..a39f435 --- /dev/null +++ b/frontend/src/components/how-to-play/quit-game/Description.mdx @@ -0,0 +1,10 @@ +import Layout from "../mdx/Layout"; +export default ({ children }) => {children}; + +#### With this transaction you can remove your ship from the game. + +Pays the min ada locked in the `ShipState` UTxO back to the ship owner. Also burns the `ShipToken` and the remaining fuel tokens. + +#### Rules to take into account: + +- the ship number has to be the one controlled by the pilot token in your wallet. \ No newline at end of file diff --git a/frontend/src/components/how-to-play/quit-game/Section.tsx b/frontend/src/components/how-to-play/quit-game/Section.tsx new file mode 100644 index 0000000..af681ce --- /dev/null +++ b/frontend/src/components/how-to-play/quit-game/Section.tsx @@ -0,0 +1,277 @@ +import { ChangeEvent, useEffect, useState } from 'react'; +import { usePathname } from 'next/navigation'; + +import Input from '@/components/ui/Input'; +import Alert from '@/components/ui/Alert'; +import CodeBlock from '@/components/ui/CodeBlock'; +import CopyButton from '@/components/ui/CopyButton'; +import ConnectWallet from '@/components/ui/ConnectWallet'; + +import Tabs, { Tab } from '@/components/how-to-play/Tabs'; +import QuitGameDescription from '@/components/how-to-play/quit-game/Description.mdx'; + +import type { ResponseData } from '@/pages/api/asteria/quit-game'; +import type { ConnectedWallet } from '@/hooks/useCardano'; +import { useChallengeStore } from '@/stores/challenge'; + +const tx3File = `tx quit_game( + ship_name: Bytes, + pilot_name: Bytes, + ship_fuel: Int, + since_slot: Int, +) { + locals { + spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, + spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, + pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, + } + + validity { + since_slot: since_slot, + } + + reference SpacetimeRef { + ref: spacetime_policy_ref, + } + + reference PelletRef { + ref: pellet_policy_ref, + } + + input ship { + from: SpacetimePolicy, + datum_is: ShipDatum, + min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipRedeemer::Quit {}, + } + + input* pilot { + from: Player, + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), + } + + mint { + amount: Fuel(ship_fuel) - Fuel(ship_fuel) - Fuel(ship_fuel), + redeemer: FuelRedeemer::BurnFuel {}, + } + + mint { + amount: AnyAsset(spacetime_policy_hash, ship_name, 1) - AnyAsset(spacetime_policy_hash, ship_name, 1) - AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipyardRedeemer::BurnShip {}, + } + + output { + to: SpacetimePolicy, + amount: ship - AnyAsset(spacetime_policy_hash, ship_name, 1) - Fuel(ship_fuel), + datum: ShipDatum {...ship}, + } + + output pilot_change { + to: Player, + amount: pilot - fees, + } + + collateral { + from: Player, + min_amount: fees, + } + +}`; + +const jsFile = `const shipName = "SHIP0"; // Replace with your ship name +const pilotName = "PILOT0"; // Replace with your pilot name +const shipFuel = 3; // Replace with the ship fuel + +const playerAddress = process.env.PLAYER_ADDRESS; +const tipSlot = 165470021; // Replace with the latest block slot + +const sinceSlot = tipSlot - 100; // 100 is just to be safe + +const args: QuitGameParams = { + player: playerAddress, + pilotName: new TextEncoder().encode(pilotName), + shipName: new TextEncoder().encode(shipName), + shipFuel, + sinceSlot, +}; + +const response = await client.quitGameTx(args);`; + +type ActionState = { + data?: { + tx?: string; + }; + errors?: Record; +}; + +interface QuitGameProps { + isActive: boolean; +} + +export default function QuitGame(props: QuitGameProps) { + const { current } = useChallengeStore(); + + const pathname = usePathname() || ''; + const [submitting, setSubmitting] = useState(false); + const [formState, setFormState] = useState({}); + const [wallet, setWallet] = useState(null); + const [address, setAddress] = useState(''); + const [shipNumber, setShipNumber] = useState(''); + const [tipSlot, setTipSlot] = useState(undefined); + + const errors = formState.errors || {}; + const dataTx = formState.data?.tx; + + useEffect(() => { + if (props.isActive) { + window.GODOT_BRIDGE?.send({ action: 'clear_placeholder' }); + } + }, [props.isActive]); + + const updateShipNumber = (event: ChangeEvent) => { + setShipNumber(event.target.value); + window.GODOT_BRIDGE?.send({ action: 'select_ship', shipNumber: event.target.value }); + } + + const handleFetchLastSlot = async () => { + const slotRes = await (await fetch("https://8000-ethereal-audience-bb83g6.us1.demeter.run/graphql", { + "headers": { "content-type": "application/json" }, + "body": "{\"query\":\"query { lastSlot { slot } }\"}", + "method": "POST" + })).json(); + + setTipSlot(slotRes.data.lastSlot.slot); + } + + const updateTipSlot = (event: ChangeEvent) => { + setTipSlot(parseInt(event.target.value)); + } + + const handleWallet = (connectedWallet: ConnectedWallet|null) => { + setWallet(connectedWallet); + setAddress(connectedWallet?.changeAddress || ''); + } + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setSubmitting(true); + setFormState({}); + const formData = new FormData(event.currentTarget); + const data = Object.fromEntries(formData.entries()); + data['network'] = current().network; + try { + const res = await fetch('/api/asteria/quit-game', { + method: 'POST', + body: JSON.stringify(data), + headers: { 'Content-Type': 'application/json' }, + }); + const jsonResponse = await res.json(); + setFormState(jsonResponse); + } catch (error) { + console.log('Error submitting form:', error); + } finally { + setSubmitting(false); + } + }; + + return ( + + + + + + + +
+ setAddress(e.target.value)} + required + /> + + + +
+
+ +
+ +
+ +
+ +
+
+ + {dataTx && ( + + {dataTx} + + )} + + {errors.global && {errors.global}} + +
+ +
+ +
+ + }> +
+ +
+
+ + }> +
+ +
+
+
+ ); +} diff --git a/frontend/src/components/ui/Input.tsx b/frontend/src/components/ui/Input.tsx index 282c7db..67d8337 100644 --- a/frontend/src/components/ui/Input.tsx +++ b/frontend/src/components/ui/Input.tsx @@ -8,15 +8,36 @@ interface Props extends InputProps { label?: string; error?: string; containerClassName?: string; -} + button?: string; + onClickButton?: () => void; + comment?: string; +} function InputElem( - { error, label, disabled, containerClassName, ...props }: Props, + { error, label, disabled, containerClassName, button, comment, onClickButton, ...props }: Props, ref: React.ForwardedRef, ) { return (
- {label && } + {label && ( +
+ + {button && ( + + )} + {comment && ( + + {comment} + + )} +
+ )} {
- {/* + How to play - */} + + ; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'POST') { + res.status(405).json({ errors: { global: 'Method not allowed' } }); + return; + } + const formData = req.body; + + const shipNumber = formData['shipNumber'] as string; + const blockSlot = formData['blockSlot'] as string; + const playerAddress = formData['playerAddress'] as string; + const pelletRef = formData['pelletRef'] as string; + const fuelAmountValue = formData['fuelAmount'] as string; + + const errors: Record = {}; + + if (!shipNumber) errors.shipNumber = 'Ship number is required'; + if (!playerAddress) errors.playerAddress = 'Player address is required'; + if (!pelletRef) errors.pelletRef = 'Pellet reference is required'; + if (!fuelAmountValue) errors.fuelAmount = 'Fuel amount is required'; + if (!blockSlot) errors.blockSlot = 'Block Slot is required'; + + const fuelAmount = Number(fuelAmountValue); + if (Number.isNaN(fuelAmount)) errors.fuelAmount = 'Fuel amount is not a number'; + + const blockSlotValue = Number(blockSlot); + if (Number.isNaN(blockSlotValue)) errors.blockSlot = 'Block Slot is not a number'; + + if (Object.keys(errors).length > 0) { + return { errors }; + } + + try { + const result = await getProtocol(formData['network']).gatherFuelTx({ + player: playerAddress, + pAmount: fuelAmount, + pilotName: new TextEncoder().encode(`PILOT${shipNumber}`), + shipName: new TextEncoder().encode(`SHIP${shipNumber}`), + pelletRef, + sinceSlot: blockSlotValue - 100, + }); + + return res.json({ + data: { + tx: result.tx, + } + }); + } catch (e: unknown) { + if (e instanceof Error) { + return res.status(400).json({ + errors: { + global: `${e.message}\nCause: ${JSON.stringify(e.cause)}` || 'Unknown error', + } + }); + } + } + + return res.status(500).json({ + errors: { + global: 'An unknown error occurred while creating the ship transaction.', + }, + }); +} diff --git a/frontend/src/pages/api/asteria/gather-token.ts b/frontend/src/pages/api/asteria/gather-token.ts new file mode 100644 index 0000000..6929ac5 --- /dev/null +++ b/frontend/src/pages/api/asteria/gather-token.ts @@ -0,0 +1,87 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +// Utils +import { getProtocol } from '@/utils/cli-protocol'; + +export type ResponseData = { + data?: { tx?: string; }; + errors?: Record; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'POST') { + res.status(405).json({ errors: { global: 'Method not allowed' } }); + return; + } + const formData = req.body; + + const shipNumber = formData['shipNumber'] as string; + const blockSlot = formData['blockSlot'] as string; + const playerAddress = formData['playerAddress'] as string; + const pelletRef = formData['pelletRef'] as string; + const fuelAmountValue = formData['fuelAmount'] as string; + const tokenAmountValue = formData['tokenAmount'] as string; + const tokenName = formData['tokenName'] as string; + const tokenPolicyHash = formData['tokenPolicyHash'] as string; + + const errors: Record = {}; + + if (!shipNumber) errors.shipNumber = 'Ship number is required'; + if (!playerAddress) errors.playerAddress = 'Player address is required'; + if (!pelletRef) errors.pelletRef = 'Pellet reference is required'; + if (!fuelAmountValue) errors.fuelAmount = 'Fuel amount is required'; + if (!blockSlot) errors.blockSlot = 'Block Slot is required'; + if (!tokenAmountValue) errors.tokenAmount = 'Token amount is required'; + if (!tokenName) errors.tokenName = 'Token name is required'; + if (!tokenPolicyHash) errors.tokenPolicyHash = 'Token policy hash is required'; + + const fuelAmount = Number(fuelAmountValue); + if (Number.isNaN(fuelAmount)) errors.fuelAmount = 'Fuel amount is not a number'; + + const blockSlotValue = Number(blockSlot); + if (Number.isNaN(blockSlotValue)) errors.blockSlot = 'Block Slot is not a number'; + + const tokenAmount = Number(tokenAmountValue); + if (Number.isNaN(tokenAmount)) errors.tokenAmount = 'Token amount is not a number'; + + if (Object.keys(errors).length > 0) { + return { errors }; + } + + try { + const result = await getProtocol(formData['network']).gatherTokenTx({ + player: playerAddress, + fuelAmount: fuelAmount, + pilotName: new TextEncoder().encode(`PILOT${shipNumber}`), + shipName: new TextEncoder().encode(`SHIP${shipNumber}`), + pelletRef, + sinceSlot: blockSlotValue - 100, + tokenAmount, + tokenName: new TextEncoder().encode(tokenName), + tokenPolicyHash: Uint8Array.from(Buffer.from(tokenPolicyHash, "hex")), + }); + + return res.json({ + data: { + tx: result.tx, + } + }); + } catch (e: unknown) { + if (e instanceof Error) { + return res.status(400).json({ + errors: { + global: `${e.message}\nCause: ${JSON.stringify(e.cause)}` || 'Unknown error', + } + }); + } + } + + return res.status(500).json({ + errors: { + global: 'An unknown error occurred while creating the ship transaction.', + }, + }); +} diff --git a/frontend/src/pages/api/asteria/mine-asteria.ts b/frontend/src/pages/api/asteria/mine-asteria.ts new file mode 100644 index 0000000..10c3931 --- /dev/null +++ b/frontend/src/pages/api/asteria/mine-asteria.ts @@ -0,0 +1,78 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +// Utils +import { getProtocol } from '@/utils/cli-protocol'; + +export type ResponseData = { + data?: { tx?: string; }; + errors?: Record; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'POST') { + res.status(405).json({ errors: { global: 'Method not allowed' } }); + return; + } + const formData = req.body; + + const shipNumber = formData['shipNumber'] as string; + const blockSlot = formData['blockSlot'] as string; + const playerAddress = formData['playerAddress'] as string; + const fuelAmountValue = formData['fuelAmount'] as string; + const mineAmountValue = formData['mineAmount'] as string; + + const errors: Record = {}; + + if (!shipNumber) errors.shipNumber = 'Ship number is required'; + if (!playerAddress) errors.playerAddress = 'Player address is required'; + if (!fuelAmountValue) errors.fuelAmount = 'Fuel amount is required'; + if (!mineAmountValue) errors.mineAmount = 'Mine amount is required'; + if (!blockSlot) errors.blockSlot = 'Block Slot is required'; + + const fuelAmount = Number(fuelAmountValue); + if (Number.isNaN(fuelAmount)) errors.fuelAmount = 'Fuel amount is not a number'; + + const mineAmount = Number(mineAmountValue); + if (Number.isNaN(mineAmount)) errors.mineAmount = 'Mine amount is not a number'; + + const blockSlotValue = Number(blockSlot); + if (Number.isNaN(blockSlotValue)) errors.blockSlot = 'Block Slot is not a number'; + + if (Object.keys(errors).length > 0) { + return { errors }; + } + + try { + const result = await getProtocol(formData['network']).mineAsteriaTx({ + player: playerAddress, + shipFuel: fuelAmount, + pilotName: new TextEncoder().encode(`PILOT${shipNumber}`), + shipName: new TextEncoder().encode(`SHIP${shipNumber}`), + mineAmount, + sinceSlot: blockSlotValue - 100, + }); + + return res.json({ + data: { + tx: result.tx, + } + }); + } catch (e: unknown) { + if (e instanceof Error) { + return res.status(400).json({ + errors: { + global: `${e.message}\nCause: ${JSON.stringify(e.cause)}` || 'Unknown error', + } + }); + } + } + + return res.status(500).json({ + errors: { + global: 'An unknown error occurred while creating the ship transaction.', + }, + }); +} diff --git a/frontend/src/pages/api/asteria/move-ship.ts b/frontend/src/pages/api/asteria/move-ship.ts index 75b9177..3cee6c4 100644 --- a/frontend/src/pages/api/asteria/move-ship.ts +++ b/frontend/src/pages/api/asteria/move-ship.ts @@ -21,23 +21,23 @@ export default async function handler( const shipNumber = formData['shipNumber'] as string; const blockSlot = formData['blockSlot'] as string; const playerAddress = formData['playerAddress'] as string; - const positionXValue = formData['positionX'] as string; - const positionYValue = formData['positionY'] as string; + const deltaXValue = formData['deltaX'] as string; + const deltaYValue = formData['deltaY'] as string; const errors: Record = {}; if (!shipNumber) errors.shipNumber = 'Ship number is required'; if (!playerAddress) errors.playerAddress = 'Player address is required'; - if (!positionXValue) errors.positionX = 'Position X is required'; + if (!deltaXValue) errors.deltaX = 'Delta X is required'; + if (!deltaYValue) errors.deltaY = 'Delta Y is required'; + if (!blockSlot) errors.blockSlot = 'Block Slot is required'; - const positionX = Number(positionXValue); - if (Number.isNaN(positionX)) errors.positionX = 'Position X is not a number'; + const deltaX = Number(deltaXValue); + if (Number.isNaN(deltaX)) errors.deltaX = 'Delta X is not a number'; - if (!positionYValue) errors.positionY = 'Position Y is required'; - const positionY = Number(positionYValue); - if (Number.isNaN(positionY)) errors.positionY = 'Position Y is not a number'; + const deltaY = Number(deltaYValue); + if (Number.isNaN(deltaY)) errors.deltaY = 'Delta Y is not a number'; - if (!blockSlot) errors.blockSlot = 'Block Slot is required'; const blockSlotValue = Number(blockSlot); if (Number.isNaN(blockSlotValue)) errors.blockSlot = 'Block Slot is not a number'; @@ -45,18 +45,23 @@ export default async function handler( return { errors }; } - // 83_176_681 <- Get this number from blockfrost using latest epoch from a latest block. try { - const distance = Math.abs(positionX) + Math.abs(positionY); + const slotRequiredPerStep = 12096; + const distance = Math.abs(deltaX) + Math.abs(deltaY); + const sinceSlot = blockSlotValue - 100; + const untilSlot = blockSlotValue + slotRequiredPerStep * distance; + const lastMoveTimestamp = Date.now() + slotRequiredPerStep * distance * 1000; const result = await getProtocol(formData['network']).moveShipTx({ - pDeltaX: positionX, - pDeltaY: positionY, player: playerAddress, - requiredFuel: distance * 60, // fuel_per_step from SpaceTime datum + pDeltaX: deltaX, + pDeltaY: deltaY, + requiredFuel: distance, shipName: new TextEncoder().encode(`SHIP${shipNumber}`), pilotName: new TextEncoder().encode(`PILOT${shipNumber}`), - tipSlot: blockSlotValue + 300, // 5 minutes from last block + sinceSlot, + untilSlot, + lastMoveTimestamp, }); return res.json({ diff --git a/frontend/src/pages/api/asteria/quit-game.ts b/frontend/src/pages/api/asteria/quit-game.ts new file mode 100644 index 0000000..c801b3b --- /dev/null +++ b/frontend/src/pages/api/asteria/quit-game.ts @@ -0,0 +1,72 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +// Utils +import { getProtocol } from '@/utils/cli-protocol'; + +export type ResponseData = { + data?: { tx?: string; }; + errors?: Record; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'POST') { + res.status(405).json({ errors: { global: 'Method not allowed' } }); + return; + } + const formData = req.body; + + const shipNumber = formData['shipNumber'] as string; + const blockSlot = formData['blockSlot'] as string; + const playerAddress = formData['playerAddress'] as string; + const fuelAmountValue = formData['fuelAmount'] as string; + + const errors: Record = {}; + + if (!shipNumber) errors.shipNumber = 'Ship number is required'; + if (!playerAddress) errors.playerAddress = 'Player address is required'; + if (!fuelAmountValue) errors.fuelAmount = 'Fuel amount is required'; + if (!blockSlot) errors.blockSlot = 'Block Slot is required'; + + const fuelAmount = Number(fuelAmountValue); + if (Number.isNaN(fuelAmount)) errors.fuelAmount = 'Fuel amount is not a number'; + + const blockSlotValue = Number(blockSlot); + if (Number.isNaN(blockSlotValue)) errors.blockSlot = 'Block Slot is not a number'; + + if (Object.keys(errors).length > 0) { + return { errors }; + } + + try { + const result = await getProtocol(formData['network']).quitGameTx({ + player: playerAddress, + shipFuel: fuelAmount, + pilotName: new TextEncoder().encode(`PILOT${shipNumber}`), + shipName: new TextEncoder().encode(`SHIP${shipNumber}`), + sinceSlot: blockSlotValue - 100, + }); + + return res.json({ + data: { + tx: result.tx, + } + }); + } catch (e: unknown) { + if (e instanceof Error) { + return res.status(400).json({ + errors: { + global: `${e.message}\nCause: ${JSON.stringify(e.cause)}` || 'Unknown error', + } + }); + } + } + + return res.status(500).json({ + errors: { + global: 'An unknown error occurred while creating the ship transaction.', + }, + }); +} diff --git a/frontend/src/pages/how-to-play/index.tsx b/frontend/src/pages/how-to-play/index.tsx index 5c59040..c9c97f0 100644 --- a/frontend/src/pages/how-to-play/index.tsx +++ b/frontend/src/pages/how-to-play/index.tsx @@ -7,6 +7,10 @@ import Menu from '@/components/how-to-play/Menu'; import Section from '@/components/how-to-play/Section'; import CreateShipSection from '@/components/how-to-play/create-ship/Section'; import MoveShipSection from '@/components/how-to-play/move-ship/Section'; +import GatherFuelSection from '@/components/how-to-play/gather-fuel/Section'; +import GatherTokenSection from '@/components/how-to-play/gather-token/Section'; +import MineAsteriaSection from '@/components/how-to-play/mine-asteria/Section'; +import QuitGameSection from '@/components/how-to-play/quit-game/Section'; export default function HowToPlay() { const { current } = useChallengeStore(); @@ -62,6 +66,22 @@ export default function HowToPlay() {
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 60892a3..f8f95c0 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,7 +1,3 @@ -// Store -import { useChallengeStore } from '@/stores/challenge'; - -// Home Components import HeroSection from '@/components/home/HeroSection'; import ChallengesSection from '@/components/home/ChallengesSection'; import GameplaySection from '@/components/home/GameplaySection'; @@ -9,10 +5,8 @@ import GameplaySection from '@/components/home/GameplaySection'; export default function Landing() { return (
- - + -
); diff --git a/frontend/tx3/bindings/protocol.ts b/frontend/tx3/bindings/protocol.ts index da1c4fa..7f95bba 100644 --- a/frontend/tx3/bindings/protocol.ts +++ b/frontend/tx3/bindings/protocol.ts @@ -18,6 +18,7 @@ export const DEFAULT_ENV_ARGS = { }; export type CreateShipParams = { + lastMoveTimestamp: ArgValue | bigint | number; // Int pPosX: ArgValue | bigint | number; // Int pPosY: ArgValue | bigint | number; // Int pilotName: ArgValue | Uint8Array; // Bytes @@ -27,36 +28,87 @@ export type CreateShipParams = { } export const CREATE_SHIP_IR = { - bytecode: "0d03030a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46010a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46000a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46020206736f757263650d0206736f757263650d0106706c61796572050e010d030c01000005fc80841e000000000007617374657269610d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef822070461757468050200000000030d0106706c6179657205000e010e020e0210010d0206736f757263650d0106706c61796572050e010d030c01000005fc80841e000000000d030c01000005fc80841e000c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370300020e010e0510020d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef82207046175746805020000000005020e0510020d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef8220704617574680502000000010e0110010d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef82207046175746805020000000c01000005fc80841e000f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430300050d0107705f706f735f78020d0107705f706f735f79020d0109736869705f6e616d65040d010a70696c6f745f6e616d65040d01087469705f736c6f74020e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c05fbc00301000d01087469705f736c6f7402020e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020300000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c05fbc0030300000000000000", + bytecode: "0d03030a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46010a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46000a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e460202036761730d02036761730d0106706c61796572050e010e010e010e010d030c01000005fc80841e000f0105000f0105040f0105060001000007617374657269610d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef8220704617574680502000000030000040d0106706c6179657205000e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020f0105000f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370300020e010e0510020d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef82207046175746805020000000005020e0510020d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef8220704617574680502000000010e0110010d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef82207046175746805020000000c01000005fc80841e000f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430300050d0107705f706f735f78020d0107705f706f735f79020d0109736869705f6e616d65040d010a70696c6f745f6e616d65040d01136c6173745f6d6f76655f74696d657374616d70020e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c050a0f0105040d0106706c6179657205000e020e020e020e0210010d02036761730d0106706c61796572050e010e010e010e010d030c01000005fc80841e000f0105000f0105040f0105060001000d030c01000005fc80841e000f0105000f01050401000d01087469705f736c6f7402020e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020300000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c050a0300000000010d020a636f6c6c61746572616c0d0106706c61796572050d030000010000", encoding: "hex", version: "v1alpha8", }; export type MoveShipParams = { + lastMoveTimestamp: ArgValue | bigint | number; // Int pDeltaX: ArgValue | bigint | number; // Int pDeltaY: ArgValue | bigint | number; // Int pilotName: ArgValue | Uint8Array; // Bytes player: ArgValue | string; // Address requiredFuel: ArgValue | bigint | number; // Int shipName: ArgValue | Uint8Array; // Bytes - tipSlot: ArgValue | bigint | number; // Int + sinceSlot: ArgValue | bigint | number; // Int + untilSlot: ArgValue | bigint | number; // Int } export const MOVE_SHIP_IR = { - bytecode: "0d03020a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46010a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46020306736f757263650d0206736f757263650d0106706c61796572050d030000000004736869700d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000300020d0109705f64656c74615f78020d0109705f64656c74615f79020570696c6f740d020570696c6f740d0106706c61796572050c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d6504050200000000020f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430300050e010e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000000d0109705f64656c74615f78020e010e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000010d0109705f64656c74615f79020e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000020e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000030d01087469705f736c6f74020e0210010d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010d72657175697265645f6675656c020d0106706c6179657205000e010e0210010d0206736f757263650d0106706c61796572050d030000000d0310010d020570696c6f740d0106706c61796572050c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d6504050200000001000d01087469705f736c6f740200010c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010d72657175697265645f6675656c0203000000000000", + bytecode: "0d03020a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46010a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46020204736869700d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000300020d0109705f64656c74615f78020d0109705f64656c74615f79020570696c6f740d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f01050200010000020f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430300050e010e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000000d0109705f64656c74615f78020e010e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000010d0109705f64656c74615f79020e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000020e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000030d01136c6173745f6d6f76655f74696d657374616d70020e0210010d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010d72657175697265645f6675656c020d0106706c6179657205000e0210010d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f0105020001000d03010d010a73696e63655f736c6f74020d010a756e74696c5f736c6f7402010e020e020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010d72657175697265645f6675656c020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010d72657175697265645f6675656c020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010d72657175697265645f6675656c020301000000010d020a636f6c6c61746572616c0d0106706c61796572050d030000010000", encoding: "hex", version: "v1alpha8", }; export type GatherFuelParams = { - pGatherAmount: ArgValue | bigint | number; // Int + pAmount: ArgValue | bigint | number; // Int + pelletRef: ArgValue | string; // UtxoRef + pilotName: ArgValue | Uint8Array; // Bytes player: ArgValue | string; // Address shipName: ArgValue | Uint8Array; // Bytes - tipSlot: ArgValue | bigint | number; // Int + sinceSlot: ArgValue | bigint | number; // Int } export const GATHER_FUEL_IR = { - bytecode: "0d03020a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46010a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46020306736f757263650d0206736f757263650d0106706c61796572050d030000000004736869700d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000301010d010f705f6761746865725f616d6f756e74020670656c6c65740d020670656c6c65740f00041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e0c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010f705f6761746865725f616d6f756e74020000000301010d010f705f6761746865725f616d6f756e7402030d0106706c6179657205000e0210010d0206736f757263650d0106706c61796572050d030000000d030f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430300050e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000000e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000010e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000020e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000030e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000040e0110010d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010f705f6761746865725f616d6f756e74020f00041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e0300030e0510020d020670656c6c65740f00041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e0c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010f705f6761746865725f616d6f756e7402000000000e0510020d020670656c6c65740f00041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e0c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010f705f6761746865725f616d6f756e740200000001041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430e0210010d020670656c6c65740f00041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e0c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010f705f6761746865725f616d6f756e74020000000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010f705f6761746865725f616d6f756e7402010d01087469705f736c6f740200000000000000", + bytecode: "0d03020a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46010a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e4602030670656c6c65740d020670656c6c657400000d010a70656c6c65745f7265660700000300010d0108705f616d6f756e740204736869700d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000301010d0108705f616d6f756e74020570696c6f740d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f01050400010000030f00041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e0300030e0510020d020670656c6c657400000d010a70656c6c65745f726566070000000e0510020d020670656c6c657400000d010a70656c6c65745f726566070000010e0510020d020670656c6c657400000d010a70656c6c65745f726566070000020e0210010d020670656c6c657400000d010a70656c6c65745f7265660700000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0108705f616d6f756e74020f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430300050e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000000e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000010e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000020e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000030e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000040e0110010d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0108705f616d6f756e74020d0106706c6179657205000e0210010d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f0105040001000d03010d010a73696e63655f736c6f740200000000010d020a636f6c6c61746572616c0d0106706c61796572050d030000010000", + encoding: "hex", + version: "v1alpha8", +}; + +export type GatherTokenParams = { + fuelAmount: ArgValue | bigint | number; // Int + pelletRef: ArgValue | string; // UtxoRef + pilotName: ArgValue | Uint8Array; // Bytes + player: ArgValue | string; // Address + shipName: ArgValue | Uint8Array; // Bytes + sinceSlot: ArgValue | bigint | number; // Int + tokenAmount: ArgValue | bigint | number; // Int + tokenName: ArgValue | Uint8Array; // Bytes + tokenPolicyHash: ArgValue | Uint8Array; // Bytes +} + +export const GATHER_TOKEN_IR = { + bytecode: "0d03020a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46010a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e4602030670656c6c65740d020670656c6c657400000d010a70656c6c65745f7265660700000300010d010b6675656c5f616d6f756e740204736869700d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000301010d010b6675656c5f616d6f756e74020570696c6f740d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f01050400010000030f00041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e0300030e0510020d020670656c6c657400000d010a70656c6c65745f726566070000000e0510020d020670656c6c657400000d010a70656c6c65745f726566070000010e0510020d020670656c6c657400000d010a70656c6c65745f726566070000020e020e0210010d020670656c6c657400000d010a70656c6c65745f7265660700000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010b6675656c5f616d6f756e74020c010d0111746f6b656e5f706f6c6963795f68617368040d010a746f6b656e5f6e616d65040d010c746f6b656e5f616d6f756e74020f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430300050e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000000e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000010e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000020e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000030e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000040e0110010d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d010b6675656c5f616d6f756e74020d0106706c6179657205000e010e0210010d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f0105040001000d030c010d0111746f6b656e5f706f6c6963795f68617368040d010a746f6b656e5f6e616d65040d010c746f6b656e5f616d6f756e7402010d010a73696e63655f736c6f740200000000010d020a636f6c6c61746572616c0d0106706c61796572050d030000010000", + encoding: "hex", + version: "v1alpha8", +}; + +export type MineAsteriaParams = { + mineAmount: ArgValue | bigint | number; // Int + pilotName: ArgValue | Uint8Array; // Bytes + player: ArgValue | string; // Address + shipFuel: ArgValue | bigint | number; // Int + shipName: ArgValue | Uint8Array; // Bytes + sinceSlot: ArgValue | bigint | number; // Int +} + +export const MINE_ASTERIA_IR = { + bytecode: "0d03030a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46010a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46000a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46020307617374657269610d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef822070461757468050200000003010004736869700d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000302000570696c6f740d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f01050400010000030f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370300020e0510020d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef8220704617574680502000000000e0510020d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef8220704617574680502000000010e0210010d0207617374657269610f00041cd55e332cd46d2abfe24dfa0558c2dedd0114d00424352eb807aac1370c01041cdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef82207046175746805020000000c0100000d010b6d696e655f616d6f756e74020f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430300050e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000000e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000010e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000020e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000030e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000040e020e0210010d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0109736869705f6675656c020d0106706c6179657205000e010e0210010d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f0105040001000d030c0100000d010b6d696e655f616d6f756e7402010d010a73696e63655f736c6f740200020e020e020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0109736869705f6675656c020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0109736869705f6675656c020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0109736869705f6675656c020301000e020e020c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020301000000010d020a636f6c6c61746572616c0d0106706c61796572050d030000010000", + encoding: "hex", + version: "v1alpha8", +}; + +export type QuitGameParams = { + pilotName: ArgValue | Uint8Array; // Bytes + player: ArgValue | string; // Address + shipFuel: ArgValue | bigint | number; // Int + shipName: ArgValue | Uint8Array; // Bytes + sinceSlot: ArgValue | bigint | number; // Int +} + +export const QUIT_GAME_IR = { + bytecode: "0d03020a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46010a01203d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46020204736869700d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000303000570696c6f740d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f01050200010000020f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430300050e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000000e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000010e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000020e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000030e0510020d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d65040502000000040e020e0210010d0204736869700f00041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020000000c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0109736869705f6675656c020d0106706c6179657205000e0210010d020570696c6f740d0106706c61796572050e010e010c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d010a70696c6f745f6e616d650405020d030f0105020001000d03010d010a73696e63655f736c6f740200020e020e020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0109736869705f6675656c020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0109736869705f6675656c020c01041c3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e07044655454c0d0109736869705f6675656c020301000e020e020c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020c01041c0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f430d0109736869705f6e616d650405020301000000010d020a636f6c6c61746572616c0d0106706c61796572050d030000010000", encoding: "hex", version: "v1alpha8", }; @@ -89,6 +141,27 @@ export class Client { }); } + async gatherTokenTx(args: GatherTokenParams): Promise { + return await this.#client.resolve({ + tir: GATHER_TOKEN_IR, + args, + }); + } + + async mineAsteriaTx(args: MineAsteriaParams): Promise { + return await this.#client.resolve({ + tir: MINE_ASTERIA_IR, + args, + }); + } + + async quitGameTx(args: QuitGameParams): Promise { + return await this.#client.resolve({ + tir: QUIT_GAME_IR, + args, + }); + } + async submit(params: SubmitParams): Promise { await this.#client.submit(params); } diff --git a/frontend/tx3/main.tx3 b/frontend/tx3/main.tx3 index 54a70d4..ca93922 100644 --- a/frontend/tx3/main.tx3 +++ b/frontend/tx3/main.tx3 @@ -19,16 +19,6 @@ policy PelletPolicy { asset Fuel = 0x3babcffc6102ec25ced40e1a24fba20371925c46f0299b2b9456360e."FUEL"; asset AdminToken = 0xdb0d968cda2cc636b28c0f377e66691a065b8004e57be5129aeef822."auth"; -type AssetRecord { - policy_id: Bytes, - asset_name: Bytes, -} - -type MaxSpeedRecord { - distance: Int, - time: Int, -} - type ShipDatum { pos_x: Int, pos_y: Int, @@ -48,14 +38,39 @@ type PelletDatum { shipyard_policy: Bytes, } -type ShipActions { +type ShipRedeemer { MoveShip { delta_x: Int, delta_y: Int, }, GatherFuel { - gather_amount: Int, + amount: Int, }, + MineAsteria, + Quit, +} + +type AsteriaRedeemer { + AddNewShip, + Mine, + ConsumeAsteria, +} + +type PelletRedeemer { + Provide { + amount: Int, + }, + ConsumePellet, +} + +type ShipyardRedeemer { + MintShip, + BurnShip, +} + +type FuelRedeemer { + MintFuel, + BurnFuel, } tx create_ship( @@ -64,9 +79,10 @@ tx create_ship( ship_name: Bytes, // Name of the ship pilot_name: Bytes, // Name of the pilot tip_slot: Int, // TODO: remove when tip_slot() implemented + last_move_timestamp: Int, ) { locals { - initial_fuel: 480, // Should be taken from spaceTime datum + initial_fuel: 5, // Should be taken from spaceTime datum ship_mint_lovelace_fee: 1000000, // Should be taken from asteria script datum spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, @@ -90,33 +106,34 @@ tx create_ship( ref: pellet_policy_ref, } - input source { + input* gas { from: Player, - min_amount: fees + Ada(ship_mint_lovelace_fee), + min_amount: fees + Ada(ship_mint_lovelace_fee) + min_utxo(pilot_token) + min_utxo(new_ship) + min_utxo(gas_change), } input asteria { from: AsteriaPolicy, min_amount: AdminToken(1), datum_is: AsteriaDatum, + redeemer: AsteriaRedeemer::AddNewShip {}, } mint { amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + AnyAsset(spacetime_policy_hash, ship_name, 1), - redeemer: (), + redeemer: ShipyardRedeemer::MintShip {}, } mint { amount: Fuel(initial_fuel), - redeemer: (), + redeemer: FuelRedeemer::MintFuel {}, } - output { + output pilot_token { to: Player, - amount: source - fees - Ada(ship_mint_lovelace_fee) + AnyAsset(spacetime_policy_hash, pilot_name, 1), + amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + min_utxo(pilot_token), } - output { + output updated_asteria { to: AsteriaPolicy, amount: asteria + Ada(ship_mint_lovelace_fee), datum: AsteriaDatum { @@ -125,17 +142,27 @@ tx create_ship( }, } - output { + output new_ship { to: SpacetimePolicy, - amount: AnyAsset(spacetime_policy_hash, ship_name, 1) + Fuel(initial_fuel), + amount: AnyAsset(spacetime_policy_hash, ship_name, 1) + Fuel(initial_fuel) + min_utxo(new_ship), datum: ShipDatum { pos_x: p_pos_x, pos_y: p_pos_y, ship_token_name: ship_name, pilot_token_name: pilot_name, - last_move_latest_time: tip_slot, + last_move_latest_time: last_move_timestamp, }, } + + output gas_change { + to: Player, + amount: gas - fees - Ada(ship_mint_lovelace_fee) - min_utxo(pilot_token) - min_utxo(new_ship), + } + + collateral { + from: Player, + min_amount: fees, + } } tx move_ship( @@ -144,7 +171,9 @@ tx move_ship( ship_name: Bytes, pilot_name: Bytes, required_fuel: Int, - tip_slot: Int, // TODO: remove when tip_slot() implemented + since_slot: Int, + until_slot: Int, + last_move_timestamp: Int, ) { locals { spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, @@ -153,7 +182,8 @@ tx move_ship( } validity { - until_slot: tip_slot, // tip_slot() + 300 + since_slot: since_slot, + until_slot: until_slot, } reference SpacetimeRef { @@ -164,7 +194,7 @@ tx move_ship( ref: pellet_policy_ref, } - input source { + collateral { from: Player, min_amount: fees, } @@ -173,20 +203,20 @@ tx move_ship( from: SpacetimePolicy, datum_is: ShipDatum, min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), - redeemer: ShipActions::MoveShip { + redeemer: ShipRedeemer::MoveShip { delta_x: p_delta_x, delta_y: p_delta_y, }, } - input pilot { + input* pilot { from: Player, - min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1), + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), } - burn { - amount: Fuel(required_fuel), - redeemer: (), + mint { + amount: Fuel(required_fuel) - Fuel(required_fuel) - Fuel(required_fuel), + redeemer: FuelRedeemer::BurnFuel {}, } output { @@ -195,30 +225,33 @@ tx move_ship( datum: ShipDatum { pos_x: ship.pos_x + p_delta_x, pos_y: ship.pos_y + p_delta_y, - last_move_latest_time: tip_slot, + last_move_latest_time: last_move_timestamp, ...ship }, } - output { + output pilot_change { to: Player, - amount: source - fees + pilot, + amount: pilot - fees, } + } -tx gather_fuel ( - p_gather_amount: Int, +tx gather_fuel( + p_amount: Int, ship_name: Bytes, - tip_slot: Int, // TODO: remove when tip_slot() implemented + pilot_name: Bytes, + pellet_ref: UtxoRef, + since_slot: Int, ) { locals { spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, } - + validity { - since_slot: tip_slot, // tip_slot() + 300 + since_slot: since_slot, } reference SpacetimeRef { @@ -229,47 +262,267 @@ tx gather_fuel ( ref: pellet_policy_ref, } - input source { - from: Player, - min_amount: fees, + input pellet { + ref: pellet_ref, + redeemer: PelletRedeemer::Provide { + amount: p_amount, + }, } input ship { from: SpacetimePolicy, datum_is: ShipDatum, min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), - redeemer: ShipActions::GatherFuel { - gather_amount: p_gather_amount, + redeemer: ShipRedeemer::GatherFuel { + amount: p_amount, }, } + input* pilot { + from: Player, + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), + } + + output { + to: PelletPolicy, + amount: pellet - Fuel(p_amount), + datum: PelletDatum {...pellet}, + } + + output { + to: SpacetimePolicy, + amount: ship + Fuel(p_amount), + datum: ShipDatum {...ship}, + } + + output pilot_change { + to: Player, + amount: pilot - fees, + } + + collateral { + from: Player, + min_amount: fees, + } + +} + +tx gather_token( + ship_name: Bytes, + pilot_name: Bytes, + pellet_ref: UtxoRef, + fuel_amount: Int, + token_name: Bytes, + token_amount: Int, + token_policy_hash: Bytes, + since_slot: Int, +) { + locals { + spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, + spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, + pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, + } + + validity { + since_slot: since_slot, + } + + reference SpacetimeRef { + ref: spacetime_policy_ref, + } + + reference PelletRef { + ref: pellet_policy_ref, + } + input pellet { - from: PelletPolicy, - datum_is: PelletDatum, - min_amount: Fuel(p_gather_amount), - redeemer: ShipActions::GatherFuel { - gather_amount: p_gather_amount, + ref: pellet_ref, + redeemer: PelletRedeemer::Provide { + amount: fuel_amount, }, } + input ship { + from: SpacetimePolicy, + datum_is: ShipDatum, + min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipRedeemer::GatherFuel { + amount: fuel_amount, + }, + } + + input* pilot { + from: Player, + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), + } + + output { + to: PelletPolicy, + amount: pellet - Fuel(fuel_amount) - AnyAsset(token_policy_hash, token_name, token_amount), + datum: PelletDatum {...pellet}, + } + output { + to: SpacetimePolicy, + amount: ship + Fuel(fuel_amount), + datum: ShipDatum {...ship}, + } + + output pilot_change { to: Player, - amount: source - fees, + amount: pilot - fees + AnyAsset(token_policy_hash, token_name, token_amount), + } + + collateral { + from: Player, + min_amount: fees, + } + +} + +tx mine_asteria( + ship_name: Bytes, + pilot_name: Bytes, + ship_fuel: Int, + mine_amount: Int, + since_slot: Int, +) { + locals { + spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, + spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, + asteria_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#0, + pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, + } + + validity { + since_slot: since_slot, + } + + reference SpacetimeRef { + ref: spacetime_policy_ref, + } + + reference AsteriaRef { + ref: asteria_policy_ref, + } + + reference PelletRef { + ref: pellet_policy_ref, + } + + input asteria { + from: AsteriaPolicy, + min_amount: AdminToken(1), + datum_is: AsteriaDatum, + redeemer: AsteriaRedeemer::Mine {}, + } + + input ship { + from: SpacetimePolicy, + datum_is: ShipDatum, + min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipRedeemer::MineAsteria {}, + } + + input* pilot { + from: Player, + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), + } + + mint { + amount: Fuel(ship_fuel) - Fuel(ship_fuel) - Fuel(ship_fuel), + redeemer: FuelRedeemer::BurnFuel {}, + } + + mint { + amount: AnyAsset(spacetime_policy_hash, ship_name, 1) - AnyAsset(spacetime_policy_hash, ship_name, 1) - AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipyardRedeemer::BurnShip {}, + } + + output { + to: AsteriaPolicy, + amount: asteria - Ada(mine_amount), + datum: AsteriaDatum {...asteria}, } output { to: SpacetimePolicy, - amount: ship + Fuel(p_gather_amount), + amount: ship - AnyAsset(spacetime_policy_hash, ship_name, 1) - Fuel(ship_fuel), datum: ShipDatum {...ship}, } + output pilot_change { + to: Player, + amount: pilot - fees + Ada(mine_amount), + } + + collateral { + from: Player, + min_amount: fees, + } + +} + +tx quit_game( + ship_name: Bytes, + pilot_name: Bytes, + ship_fuel: Int, + since_slot: Int, +) { + locals { + spacetime_policy_hash: 0x0291ae7aebaf064b785542093c2b13169effb34462301e68d4b44f43, + spacetime_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#1, + pellet_policy_ref: 0x3d308c0f3deb1eff764cbb765452c53d30704748681d7acd61c7775aeb8a8e46#2, + } + + validity { + since_slot: since_slot, + } + + reference SpacetimeRef { + ref: spacetime_policy_ref, + } + + reference PelletRef { + ref: pellet_policy_ref, + } + + input ship { + from: SpacetimePolicy, + datum_is: ShipDatum, + min_amount: AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipRedeemer::Quit {}, + } + + input* pilot { + from: Player, + min_amount: AnyAsset(spacetime_policy_hash, pilot_name, 1) + fees + min_utxo(pilot_change), + } + + mint { + amount: Fuel(ship_fuel) - Fuel(ship_fuel) - Fuel(ship_fuel), + redeemer: FuelRedeemer::BurnFuel {}, + } + + mint { + amount: AnyAsset(spacetime_policy_hash, ship_name, 1) - AnyAsset(spacetime_policy_hash, ship_name, 1) - AnyAsset(spacetime_policy_hash, ship_name, 1), + redeemer: ShipyardRedeemer::BurnShip {}, + } + output { - to: PelletPolicy, - amount: pellet - Fuel(p_gather_amount), - datum: PelletDatum { - pos_x: pellet.pos_x, - pos_y: pellet.pos_y, - shipyard_policy: spacetime_policy_hash, - }, + to: SpacetimePolicy, + amount: ship - AnyAsset(spacetime_policy_hash, ship_name, 1) - Fuel(ship_fuel), + datum: ShipDatum {...ship}, + } + + output pilot_change { + to: Player, + amount: pilot - fees, + } + + collateral { + from: Player, + min_amount: fees, } + } \ No newline at end of file diff --git a/godot-visualizer/project.godot b/godot-visualizer/project.godot index 9600cd7..9826ee6 100644 --- a/godot-visualizer/project.godot +++ b/godot-visualizer/project.godot @@ -12,7 +12,7 @@ config_version=5 config/name="visualizer" run/main_scene="res://scenes/main.tscn" -config/features=PackedStringArray("4.4", "GL Compatibility") +config/features=PackedStringArray("4.5", "GL Compatibility") boot_splash/bg_color=Color(0.0392157, 0.0392157, 0.0392157, 1) config/icon="res://resources/asteria.svg" diff --git a/godot-visualizer/scripts/map.gd b/godot-visualizer/scripts/map.gd index aa6049d..70dde38 100644 --- a/godot-visualizer/scripts/map.gd +++ b/godot-visualizer/scripts/map.gd @@ -202,6 +202,11 @@ func _on_follow_ship_selected(ship_number: String) -> void: selected_ship_position_changed.emit(ship_data.position * Global.get_cell_size() + initial_position) Global.set_selected_ship(ship_data) _on_main_dataset_updated() + + JavaScriptBridge.eval( + "parent.window.postMessage({ action: 'ship_selected', position: { x: %s, y: %s }, payload: %s })" + % [ship_data.position.x, ship_data.position.y, ship_data.json()] + ) func _on_next_position_reset() -> void: