| ! -------------------------------------------------------------------------- | |
| ! "ROBOTS": Another abuse of the Z-machine, Copied Right in 1995 | |
| ! | |
| ! I got the idea of writing this when seeing Andrew Plotkin's much more | |
| ! interesting game "Freefall". I used his code for reference about some | |
| ! technical details. | |
| ! | |
| ! I don't know who originally came up with this game idea. I have seen it | |
| ! under the name "DALEKS", but that version was a bit different. This one | |
| ! uses (almost) both the layout and the key configuration of the version | |
| ! which can, at least usually, be found in /usr/games on Unix systems. | |
| ! | |
| ! To compile this program, you need to use Inform 5.5 or later. | |
| ! | |
| ! This program was written by Torbj|rn Andersson, d91tan@Minsk.DoCS.UU.SE | |
| ! | |
| ! Feel free to do whatever you want with this code, but if you find any | |
| ! bug, please try to find some way of telling me. If you like it, don't | |
| ! forget to smile. If you think you can make money from it, you are more | |
| ! optimistic than I thought. | |
| ! | |
| ! Release 2 makes some slight optimizations (I hope) to the code which | |
| ! detects collisions between robots, and makes a few cosmetic changes. | |
| ! | |
| ! Release 3 cleans up some of the code a bit, makes some further | |
| ! optimizations to the collision-detection and allows the user to keep | |
| ! playing even when the maximum number of robots have been reached. (It | |
| ! just won't increase the number of robots any further.) For this reason, | |
| ! I've lowered the maximum number of robots from 500 to 300, which should | |
| ! still be more than enough. | |
| ! | |
| ! Release 4 changes @read_char 1 0 0 key; to @read_char 1 key; since I | |
| ! have been informed (no pun intended) that the former is considered | |
| ! illegal by some intepreters. Of course, I then felt obliged to test the | |
| ! limits of portability again by changing it to use @@156 for the non- | |
| ! standard character in my name. To make the new release a bit more worth- | |
| ! while, I've cleaned up MoveRobots() a bit (I hope), and added a variable | |
| ! to keep track of bonus earned while waiting. | |
| ! | |
| ! Release 5; I was told that @beep without argument crashed an | |
| ! interpreter (I don't know which one), so I changed it to use | |
| ! @sound_effect 1 instead, to comply with the most recent version of the | |
| ! Z-machine specification. | |
| ! -------------------------------------------------------------------------- | |
| Switches xv5s; | |
| Release 5; | |
| ! Game constants | |
| Constant PrefLines 24; ! This is the screen size for which | |
| Constant PrefCols 80; ! the game is designed. | |
| Constant FieldRows 22; ! Size of the playing field. | |
| Constant FieldColumns 59; | |
| Constant FieldSize 1298; ! FieldRows * FieldColumns | |
| Constant RobotScore 10; ! Points for killing one robot | |
| Constant BonusScore 11; ! Ditto while 'W'aiting. | |
| Constant Robot '+'; ! Symbols used on the game field | |
| Constant Player '@'; | |
| Constant JunkHeap '*'; | |
| Constant Empty 0; | |
| Constant IncRobots 10; ! Robots added for each level | |
| Constant MaxRobots 300; ! Max number of robots | |
| ! Global variables | |
| Global sw__var = 0; ! Needed for switch() and such | |
| Global score = 0; ! Current score | |
| Global high_score = 0; ! Highest score this session | |
| Global waiting = 0; ! Set when 'W'aiting | |
| Global wait_bonus = 0; ! Bonus while waiting | |
| Global beep_flag = 1; ! Sound on/off | |
| Global player_x = 0; ! Player's current position | |
| Global player_y = 0; ! - " - | |
| Global num_robots = IncRobots; ! Number of robots on level | |
| Global active_robots = IncRobots; ! Number of live robots on level | |
| ! The PlayingField contains information about robots and junkheaps (though not | |
| ! about the player). It is used for fast lookup when moving the player or a | |
| ! robot. An alternative solution would be to keep an array of the junkheaps, | |
| ! similar to RobotList, which would save memory but which would also be much | |
| ! less efficient. | |
| Global PlayingField -> FieldSize; | |
| ! The RobotList encodes the individual robots' positions in words (two bytes), | |
| ! and is used to speed up the operations which work on all robots. It would be | |
| ! possible to search PlayingField, but that would be impractical. It is assumed | |
| ! that no player will survive long enough for the array to overflow. | |
| Global RobotList --> MaxRobots; | |
| ! -------------------------------------------------------------------------- | |
| ! MAIN FUNCTION | |
| ! | |
| ! The earliest-defined routine is not allowed to have local variables, so | |
| ! I have put all that needs local variables in separate functions. | |
| ! -------------------------------------------------------------------------- | |
| [ Main; | |
| TestScreenSize(); | |
| print "^^"; | |
| Banner(); | |
| print "^~You can't miss it,~ they said. ~A white house in a clearing \ | |
| with a small mailbox outside; just open the kitchen window and \ | |
| the entrance to the Great Underground Empire isn't far away.~^^"; | |
| print "You found the house and the window all right, and a trapdoor \ | |
| leading down. But as the trapdoor crashed shut behind you, you \ | |
| realized that something was very wrong. Surely the GUE shouldn't \ | |
| look like a large square room with bare walls, and what about \ | |
| those menacing shapes advancing towards you ...?^^"; | |
| print "[Press any key to continue.]^"; | |
| ReadKeyPress(); | |
| while (PlayGame() ~= 0); | |
| ! These magic incantation should restore the screen to something more | |
| ! normal (for a text adventure). Actually, I'm not 100% sure how much of | |
| ! this is really needed. | |
| @set_cursor 1 1; | |
| @split_window 0; | |
| @erase_window $ffff; | |
| @set_window 0; | |
| print "^^The idea of writing something like this came from seeing Andrew \ | |
| Plotkin's much more interesting game 'Freefall'. It's really \ | |
| quite amusing to see what the Z-machine can do with a little \ | |
| persuasion.^^"; | |
| print "Torbj@@156rn Andersson, 1995^^"; | |
| print "[Press any key to exit.]^"; | |
| ReadKeyPress(); | |
| quit; | |
| ]; | |
| [ Banner i; | |
| style bold; print "ROBOTS"; style roman; | |
| print " - Another abuse of the Z-Machine^"; | |
| print "A nostalgic diversion by Torbj@@156rn Andersson^"; | |
| print "Release ", (0-->1) & $03ff, " / Serial number "; | |
| for (i = 18 : i < 24 : i++) | |
| print (char) 0->i; | |
| print " / Inform v"; | |
| inversion; | |
| new_line; | |
| ]; | |
| ! -------------------------------------------------------------------------- | |
| ! THE ACTUAL GAME | |
| ! -------------------------------------------------------------------------- | |
| ! This function plays a game of "robots" | |
| [ PlayGame x y n key got_keypress meta old_score; | |
| ! Clear the screen, initialize the game board and draw it on screen. | |
| y = FieldRows + 2; | |
| @erase_window $ffff; | |
| @split_window y; | |
| @set_window 1; | |
| score = 0; | |
| num_robots = IncRobots; | |
| active_robots = IncRobots; | |
| InitPlayingField(); | |
| DrawPlayingField(); | |
| ! "Infinite" loop (there are 'return' statements to terminate it) which | |
| ! waits for keypresses and moves the robots. The 'meta' variable is used | |
| ! to keep track of whether or not anything game-related really happened. | |
| for (::) { | |
| meta = 0; | |
| ! Remember the player's old position. | |
| x = player_x; | |
| y = player_y; | |
| ! Wait for a valid keypress. If the player is 'W'aiting, it is the | |
| ! same as if he or she is constantly pressing the '.' key, except the | |
| ! robots will actually be allowed to walk into the player. | |
| for (got_keypress = 0 : got_keypress == 0:) { | |
| got_keypress = 1; | |
| if (waiting == 0) | |
| key = ReadKeyPress(); | |
| else | |
| key = '.'; | |
| if (wait_bonus == -1) { | |
| wait_bonus = 0; | |
| n = FieldColumns + 4; | |
| @set_cursor 24 n; | |
| spaces(10); | |
| } | |
| switch (key) { | |
| '.': | |
| 'Y': player_x--; player_y--; | |
| 'K': player_y--; | |
| 'U': player_x++, player_y--; | |
| 'H': player_x--; | |
| 'L': player_x++; | |
| 'B': player_x--; player_y++; | |
| 'J': player_y++; | |
| 'N': player_x++; player_y++; | |
| 'T': | |
| GetNewPlayerPos(); | |
| 'W': | |
| old_score = score; | |
| wait_bonus = 0; | |
| waiting = 1; | |
| 'Q': | |
| return AnotherGame(); | |
| 'R': | |
| DrawPlayingField(); | |
| meta = 1; | |
| 'S': | |
| if (beep_flag == 0) | |
| beep_flag = 1; | |
| else | |
| beep_flag = 0; | |
| meta = 1; | |
| default: | |
| got_keypress = 0; | |
| DoBeep(); | |
| } | |
| } | |
| ! If the command was a movement command, check if the player is moving | |
| ! to a safe spot or not. (Exception: Teleports are inherently risky, | |
| ! but will always put you in an empty spot on the game board, so don't | |
| ! warn about that. | |
| ! | |
| ! If the player has moved, redraw that part of the game board. | |
| ! | |
| ! If the move is not accepted, make sure the player remains at the | |
| ! original location, warn him or her, and make sure the robots don't | |
| ! move. | |
| if (meta == 0) { | |
| if (key == 'T' || | |
| (InsideField(player_x, player_y) ~= 0 && | |
| SafeSpot(player_x, player_y) ~= 0)) { | |
| if (x ~= player_x || y ~= player_y) { | |
| DrawObject(x, y, ' '); | |
| DrawObject(player_x, player_y, Player); | |
| } | |
| } else { | |
| if (waiting == 0) { | |
| player_x = x; | |
| player_y = y; | |
| DoBeep(); | |
| meta = 1; | |
| } | |
| } | |
| ! If the player made a valid move, move the robots. | |
| if (meta == 0) | |
| MoveRobots(); | |
| ! The robots have moved and dead robots have been handled by | |
| ! MoveRobots(). Now it's time to see if the player survived, and | |
| ! maybe even won the game. | |
| if (GetPiece(player_x, player_y) == Empty) { | |
| if (active_robots == 0) { | |
| waiting = 0; | |
| UpdateScore(0); | |
| num_robots = num_robots + IncRobots; | |
| if (num_robots > MaxRobots) | |
| num_robots = MaxRobots; | |
| InitPlayingField(); | |
| DrawPlayingField(); | |
| } else | |
| DrawObject(player_x, player_y, 0); | |
| } else { | |
| DrawObject(player_x, player_y, 0); | |
| print "AARRrrgghhhh...."; | |
| if (waiting ~= 0) { | |
| score = old_score; | |
| waiting = 0; | |
| } | |
| UpdateScore(0); | |
| return AnotherGame(); | |
| } | |
| } | |
| } | |
| ]; | |
| ! This function moves the robots and handles collisions between robots and | |
| ! other robots or junkheaps. | |
| [ MoveRobots i j robot_x robot_y hit; | |
| ! Traverse the list of active robots. At this point there should be no | |
| ! 'dead' robots in the list. | |
| for (i = 0, hit = 0 : i < active_robots : i++) { | |
| robot_x = RobotX(i); | |
| robot_y = RobotY(i); | |
| ! Remove the robot from the playing field and the game board (though | |
| ! not from the robot list. | |
| DrawObject(robot_x, robot_y, ' '); | |
| PutPiece(robot_x, robot_y, Empty); | |
| ! The robot will always try to move towards the player, regardless of | |
| ! obstacles. | |
| if (robot_x ~= player_x) { | |
| if (robot_x < player_x) | |
| robot_x++; | |
| else | |
| robot_x--; | |
| } | |
| if (robot_y ~= player_y) { | |
| if (robot_y < player_y) | |
| robot_y++; | |
| else | |
| robot_y--; | |
| } | |
| ! Any robot moving onto a junk heap is destroyed. Otherwise, the robot | |
| ! is inserted on the playing field at its new location. | |
| if (GetPiece(robot_x, robot_y) == JunkHeap) { | |
| hit = 1; | |
| RobotList-->i = -1; | |
| UpdateScore(1); | |
| } else { | |
| ! Draw the robot on screen to reduce the flicker. The final | |
| ! drawing is done in the next loop, as some robots may have | |
| ! been erased by other moving robots. | |
| DrawObject(robot_x, robot_y, Robot); | |
| PutRobot(robot_x, robot_y, i); | |
| } | |
| } | |
| ! If a robot was removed, clean up the robot list. | |
| if (hit ~= 0) | |
| CleanRobotList(); | |
| ! To make sure that no robot is accidentally 'removed' from the board | |
| ! (which could happen if a robot onto another robot before the other | |
| ! robot moves, since the other robot will 'blank' its old position on | |
| ! the board) we draw all the robots again. | |
| for (i = 0, hit = 0 : i < active_robots : i++) { | |
| robot_x = RobotX(i); | |
| robot_y = RobotY(i); | |
| ! If two robots ended up in the same position, there was a | |
| ! collision. I don't know if it's a good idea or not, but I | |
| ! don't want to do the robot-removal yet, so just set a flag | |
| ! that there are collisions to detect. | |
| if (GetPiece(robot_x, robot_y) == Robot) | |
| hit = 1; | |
| DrawObject(robot_x, robot_y, Robot); | |
| PutPiece(robot_x, robot_y, Robot); | |
| } | |
| ! If no robots collided, all is done. | |
| if (hit == 0) | |
| rtrue; | |
| CleanRobotList(); | |
| ! At least one collision occured. It's time to find out which robots | |
| ! collided. This code is the game's major cause of slowdown. | |
| for (i = 0, hit = 0 : i < active_robots - 1 : i++) { | |
| for (j = i + 1 : j < active_robots : j++) { | |
| if (RobotList-->i ~= -1 && RobotList-->i == RobotList-->j) { | |
| robot_x = RobotX(i); | |
| robot_y = RobotY(i); | |
| PutPiece(robot_x, robot_y, JunkHeap); | |
| DrawObject(robot_x, robot_y, JunkHeap); | |
| RobotList-->i = -1; | |
| RobotList-->j = -1; | |
| ! Don't give the player any points for robots killing him/her | |
| if (robot_x ~= player_x || robot_y ~= player_y) | |
| UpdateScore(2); | |
| ! Since RobotList-->i now is -1, we won't find any other | |
| ! robots on the same position, so terminate the inner loop. | |
| ! I don't know if it'd be better to save the position of | |
| ! robot i, and follow the loop to its very end. | |
| break; | |
| } | |
| } | |
| } | |
| ! I know at least one collision occured, and therefore I know that robots | |
| ! have been removed. | |
| CleanRobotList(); | |
| ! And even now we are not done: What if three robots went to the same | |
| ! square? In that case, there should be a robot sitting on a junkheap | |
| ! now. This can only happen if the previous loop detected a collision | |
| ! between two robots. | |
| for (i = 0, hit = 0 : i < active_robots : i++) { | |
| robot_x = RobotX(i); | |
| robot_y = RobotY(i); | |
| if (GetPiece(robot_x, robot_y) == JunkHeap) { | |
| hit = 1; | |
| RobotList-->i = -1; | |
| if (robot_x ~= player_x || robot_y ~= player_y) | |
| UpdateScore(1); | |
| } | |
| } | |
| if (hit ~= 0) | |
| CleanRobotList(); | |
| ]; | |
| ! -------------------------------------------------------------------------- | |
| ! THE GAME BOARD | |
| ! -------------------------------------------------------------------------- | |
| ! These two functions are used for printing the game board. This is done both | |
| ! when starting on a level and when using the 'R'edraw command. | |
| [ DrawPlayingField i x y; | |
| @erase_window 1; | |
| ! Draw the border around the game board. | |
| DrawHorizontalLine(1); | |
| DrawHorizontalLine(FieldRows + 2); | |
| x = FieldColumns + 2; | |
| for (i = 2 : i <= FieldRows + 1 : i++) { | |
| @set_cursor i 1; print (char) '|'; | |
| @set_cursor i x; print (char) '|'; | |
| } | |
| ! Draw the robots on the game board. | |
| for (i = 0 : i < active_robots : i++) | |
| DrawObject(RobotX(i), RobotY(i), Robot); | |
| ! If some robots have died, we have to traverse the entire PlayingField | |
| ! looking for junkheaps. Fortunately, this only happens when 'R'edrawing | |
| ! the screen, which shouldn't be very often. | |
| if (active_robots < num_robots) { | |
| for (x = 0 : x < FieldColumns : x++) { | |
| for (y = 0 : y < FieldRows : y++) { | |
| if (GetPiece(x, y) == JunkHeap) { | |
| DrawObject(x, y, JunkHeap); | |
| } | |
| } | |
| } | |
| } | |
| ! Put some help text to the right of the game board. | |
| x = FieldColumns + 4; | |
| @set_cursor 1 x; print "Directions:"; | |
| @set_cursor 3 x; print "y k u"; | |
| @set_cursor 4 x; print " @@92|/ "; | |
| @set_cursor 5 x; print "h-.-l"; | |
| @set_cursor 6 x; print " /|@@92 "; | |
| @set_cursor 7 x; print "b j n"; | |
| @set_cursor 9 x; print "Commands:"; | |
| @set_cursor 11 x; print "w: wait for end"; | |
| @set_cursor 12 x; print "t: teleport"; | |
| @set_cursor 13 x; print "q: quit"; | |
| @set_cursor 14 x; print "r: redraw screen"; | |
| @set_cursor 16 x; print "Legend:"; | |
| @set_cursor 18 x; print (char) Robot, ": robot"; | |
| @set_cursor 19 x; print (char) JunkHeap, ": junk heap"; | |
| @set_cursor 20 x; print (char) Player, ": you"; | |
| if (wait_bonus > 0) { | |
| @set_cursor 24 x; print "Bonus: ", wait_bonus; | |
| wait_bonus = -1; | |
| } | |
| @set_cursor 22 x; print "Score: ", score; | |
| @set_cursor 23 x; print "High: ", high_score; | |
| ! Finally, draw the player on the game board. | |
| DrawObject(player_x, player_y, Player); | |
| DrawObject(player_x, player_y, 0); | |
| ]; | |
| [ DrawHorizontalLine row i; | |
| @set_cursor row 1; | |
| print (char) '+'; | |
| for (i = 0 : i < FieldColumns : i++) | |
| print (char) '-'; | |
| print (char) '+'; | |
| ]; | |
| ! -------------------------------------------------------------------------- | |
| ! HELP FUNCTIONS | |
| ! -------------------------------------------------------------------------- | |
| ! Test the screen size. The game will look very odd, and maybe not run at all, | |
| ! if the screen is too small. | |
| [ TestScreenSize screen_height screen_width; | |
| screen_height = 0->32; | |
| screen_width = 0->33; | |
| if (screen_height < PrefLines || screen_width < PrefCols) | |
| print "^^[The interpreter thinks your screen is ", screen_width, | |
| (char) 'x', screen_height, ". It is recommended that you \ | |
| use at least ", PrefCols, (char) 'x', PrefLines, ".]"; | |
| ]; | |
| ! Test is a coordinate is safe to move it, ie that | |
| ! | |
| ! a) There is no junkheap on it | |
| ! b) There are no robots on any adjacent coordinate | |
| [ SafeSpot xpos ypos x y; | |
| if (GetPiece(xpos, ypos) == JunkHeap) | |
| rfalse; | |
| for (x = xpos - 1 : x <= xpos + 1 : x++) { | |
| for (y = ypos - 1 : y <= ypos + 1 : y++) { | |
| if (InsideField(x, y) ~= 0 && GetPiece(x, y) == Robot) | |
| rfalse; | |
| } | |
| } | |
| rtrue; | |
| ]; | |
| ! Update the score after killing 'n' robots. If 'n' is 0 it will simply | |
| ! redraw the score. If we are 'W'aiting, the score is not written since it | |
| ! is not known whether or not the player will actually get points until he | |
| ! or she has survived the entire level. | |
| [ UpdateScore n x; | |
| if (n ~= 0) { | |
| if (waiting ~= 0) { | |
| wait_bonus = wait_bonus + n * (BonusScore - RobotScore); | |
| score = score + (n * BonusScore); | |
| } else | |
| score = score + (n * RobotScore); | |
| } | |
| if (waiting == 0) { | |
| x = FieldColumns + 11; | |
| @set_cursor 22 x; print score; | |
| if (score > high_score) { | |
| high_score = score; | |
| @set_cursor 23 x; print high_score; | |
| } | |
| } | |
| ]; | |
| ! Ask the user if he or she wants to play another game | |
| [ AnotherGame x; | |
| x = FieldColumns + 4; | |
| @set_cursor 24 x; | |
| print "Another game? "; | |
| for (::) { | |
| switch (ReadKeyPress()) { | |
| 'Y': rtrue; | |
| 'N': rfalse; | |
| } | |
| } | |
| ]; | |
| ! Get a new position for the player. This is used both when 'T'eleporting and | |
| ! when starting on a new level, and ensures that the player will not land on | |
| ! any robot or junkpile. The player may, however, land right next to a robot, | |
| ! which is fatal when 'T'eleporting, and uncomfortable when starting on a new | |
| ! level. | |
| [ GetNewPlayerPos; | |
| for (::) { | |
| player_x = random(FieldColumns) - 1; | |
| player_y = random(FieldRows) - 1; | |
| if (GetPiece(player_x, player_y) == Empty) | |
| break; | |
| } | |
| ]; | |
| ! The code which checks for robots colliding is horrendously inefficient, so | |
| ! in order to speed it up as the game proceeds, remove 'dead' robots from the | |
| ! list and keep a counter of 'active' robots. | |
| [ CleanRobotList i j; | |
| for (i = 0, j = 0 : i < active_robots : i++) { | |
| if (RobotList-->i ~= -1) { | |
| RobotList-->j = RobotList-->i; | |
| j++; | |
| } | |
| } | |
| active_robots = j; | |
| ]; | |
| ! -------------------------------------------------------------------------- | |
| ! INITIALIZATION | |
| ! -------------------------------------------------------------------------- | |
| ! Initialize the PlayingField and RobotList | |
| [ InitPlayingField i x y; | |
| active_robots = num_robots; | |
| for (i = 0 : i < FieldSize : i++) | |
| PlayingField->i = Empty; | |
| for (i = 0 : i < num_robots : i++) { | |
| for (::) { | |
| x = random(FieldColumns) - 1; | |
| y = random(FieldRows) - 1; | |
| if (GetPiece(x, y) == Empty) { | |
| PutPiece(x, y, Robot); | |
| PutRobot(x, y, i); | |
| break; | |
| } | |
| } | |
| } | |
| GetNewPlayerPos(); | |
| ]; | |
| ! -------------------------------------------------------------------------- | |
| ! PRIMITIVES | |
| ! -------------------------------------------------------------------------- | |
| ! Produce an annoying 'beep', if the sound is turned on. The sound is toggled | |
| ! with 'S', which, since it isn't properly documented, must surely be a bug | |
| ! rather than a feature. :-) | |
| [ DoBeep; | |
| if (beep_flag ~= 0) | |
| @sound_effect 1; | |
| ]; | |
| ! Read a single character from stream 1 (the keyboard) and return it. If the | |
| ! character is lower-case, it is translated to upper-case first. | |
| [ ReadKeyPress x; | |
| @read_char 1 x; | |
| if (x >= 'a' && x <= 'z') | |
| x = x - ('a' - 'A'); | |
| return x; | |
| ]; | |
| ! These two primitives are used for reading the PlayingField and inserting new | |
| ! values in it respectively. | |
| [ GetPiece x y; | |
| return PlayingField->(y * FieldColumns + x); | |
| ]; | |
| [ PutPiece x y type; | |
| PlayingField->(y * FieldColumns + x) = type; | |
| ]; | |
| ! These three primitives are used for getting and setting the coordinates of | |
| ! a robot respectively. A dead robot is marked as -1 in RobotList, and it is | |
| ! up to the calling functions to test this if necessary. | |
| [ RobotX n; | |
| return (RobotList-->n) / 256; | |
| ]; | |
| [ RobotY n; | |
| return (RobotList-->n) % 256; | |
| ]; | |
| [ PutRobot x y n; | |
| RobotList-->n = x * 256 + y; | |
| ]; | |
| ! Print a character on the game board. Note that it is up to the calling | |
| ! function to make sure that this bears any resemblance to what is actually | |
| ! stored in the PlayingField. | |
| [ DrawObject x y c; | |
| x = x + 2; | |
| y = y + 2; | |
| @set_cursor y x; | |
| if (c ~= 0) | |
| print (char) c; | |
| ]; | |
| ! Primitive for testing if a coordinate is inside the game board. | |
| [ InsideField x y; | |
| if (x >= 0 && y >= 0 && x < FieldColumns && y < FieldRows) | |
| rtrue; | |
| rfalse; | |
| ]; | |
| end; | |
Xet Storage Details
- Size:
- 21.2 kB
- Xet hash:
- 03b12fa75db2fd3128b8e0206ac862bc3c3634fcb8be020a8fa82759b054cb4e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.