0 votes
by (280 points)

Hi.

I've recently switched from Harlowe to SugarCube and am having a hard time getting used to the syntax. However, I already feel that I can do more with the new format and I've decided to stick to it. Especially since a big chunk of my game requires text input from the player, which SugarCube 2.18 seems to handle well.

I am currently writing a simple math-based game for my students and would like for them to be able to check their own personal high scores every time they return to the game. I've fiddled around with autosaves and what-not and am quite happy about how it works. Unfortunately I haven't been able to find a good way to store the player score for each run and list it properly in my score board passage.

The player sets their name to $playerName in the beginning which works just fine. They also have a $currentScore variable that updates throughout the game. I've broken the code so many times now that I don't have any example code for you to look at, but here is how I would break it down:

  1. Store the player name (string) AND final score (integer) in an array of some kind
  2. Use a sort-function on the array to put the highest player score on top
  3. Only keep the ten highest scores and remove the rest from the array
  4. Show the scoreboard to the player
  5. Save the current state of the game and restart the game

How would you do it?

Please advice! 

2 Answers

+1 vote
by (159k points)
selected by
 
Best answer

WARNING:
A standard Twine Story HTML file has no server component so by default all data saved within a story is persisted within the local storage area of the Reader's web-browser, which means that one Reader can't see the data of another Reader and that a Reader can only see their own saved data if they are using the same web-browser on the same machine.that they previously used to view the story.
So in your particular use-case this means that the scoreboard will only contain the results of the current Reader, and that those results are only available if the Reader is using the same web-browser on the same machine as before.

You could use widgets like the following two to persist the scoreboard between each play through and to show the current scoreboard within the story. The code needs to be placed within a widget tagged passage.

<<widget "updatescoreboard">>
	\<<silently>>
		/% Create the scoreboard variable if it doesn't exist. %/
		<<if ndef $scoreboard>>
			<<set $scoreboard to []>>
		<</if>>

		/% Add the current name and score to the end of the array. %/
		<<set $scoreboard.push([$playerName, $currentScore])>>

		/% Sort the array. %/
		<<run $scoreboard.sort(function (a, b) { return b[1] - a[1]; })>>

		/% Limit the length of the array to 10 elements. %/
		<<if $scoreboard.length > 10>>
			<<run $scoreboard.deleteAt(10)>>
		<</if>>
		
		/% Persist the scoreboard story variable. %/
		<<remember $scoreboard>>
	<</silently>>
<</widget>>


<<widget "showscoreboard">>
	\<<nobr>>
		<<if def $scoreboard>>
			<<for _i, _pair range $scoreboard>>
				<br><<print _i + 1>>. _pair[0]: _pair[1]
			<</for>>
		<</if>>
	<</nobr>>\
<</widget>>

The following two passages demonstrate the usage of the above two widgets.

1. The passage displaying the scoreboard, this was my Start passage.

/% Setup some debuging data to use instead of the actual story variables. %/
<<set $playerName to "Abcde", $currentScore to random(10, 50)>>\

Player Name: $playerName
Player Score: $currentScore

Scoreboard: <<showscoreboard>>

[[Final page of game|Final Page]]

<<link "Clear Scoreboard">>
	\<<forget $scoreboard>>
	\<<script>>Engine.restart();<</script>>
<</link>>

 

2. The final passage where you update the current value of the scoreboard, my Final Page passage.

<<updatescoreboard>>\
Congratulation $playerName you scored $currentScore points.

<<link "Replay Game">>
	<<script>>Engine.restart();<</script>>
<</link>>

 

by (280 points)
Thank you for the heads up greyelf! I was aware of the problems you describe. I just learned about widgets in SugarCube this morning and they seem to be really useful. I'm going to try out your solution and see how I like it.

As for solving those problems in the future and making this math game really competitive I'm thinking that there might be a way to store a global high score in a Google spreadsheet or setting up a web server for it. I do have a Raspberry Pi3 running that could be useful for that.

However, since my target players are all students at the school where I work I won't put too much effort into such a solution right now. First I need a fully functional piece of code. =)

Again, thanks!
by (100 points)
Hey greyelf, I really like the look of this code but I can't get it to work -- I'm guessing I'm probably just making a newbie mistake.  I tried to implement what you have above but instead of printing the scoreboard I'm just getting a row of ". _pair[0]: _pair[1]" lines, instead of actual playernames and score values.  Any ideas?
+1 vote
by (710 points)

This is how I did it for my game:

First, put everything you want to store into an object. For me that was a whole bunch of things, but for you it's just $playerName and $currentScore.

<<set $hiscore = {
  name: $playerName,
  final_score: $currentScore
} >>

Then add it to the array. Here's the code from my game, slightly modified to store 10 hiscores rather than 100:

	/* Create high score table if it doesn't exist */
	<<if !$PERM_hiscores>>
		<<remember $PERM_hiscores = []>>
	<</if>>

	/* Add new high score in appropriate place in table */
	<<set _saved = false>>
	<<for _n = 0; _n < $PERM_hiscores.length; _n++>>
		<<if $hiscore.final_score >= $PERM_hiscores[_n].final_score>>
			<<remember $PERM_hiscores.splice(_n, 0, $hiscore)>>
			<<remember $PERM_most_recent_score = _n>>
			<<set _saved = true>>
			<<break>>
		<</if>>
	<</for>>

	/* Remember a max of 10 high scores, but always remember the most recent one. */
	<<if $PERM_hiscores.length > 9 & _saved == false>>
		<<remember $PERM_hiscores = $PERM_hiscores.slice(0,9)>>
	<<elseif $PERM_hiscores.length > 100>>
		<<remember $PERM_hiscores = $PERM_hiscores.slice(0,10)>>
	<</if>>

	/* If we didn't add it already, add it to the end. */
	<<if !_saved>>
		<<remember $PERM_hiscores.push($hiscore)>>
		<<remember $PERM_most_recent_score = $PERM_hiscores.length-1>>
	<</if>>

Using <<remember>> rather than <<set>> means that the variable should persist across game loads. I give names of remembered variables the PERM_ prefix to remind me which ones they are.

Then you can display the hiscores by iterating over the array:

<<for _n = 0; _n < $PERM_hiscores.length; _n++>>
  NAME: <<print $PERM_hiscores[_n].name>>
  SCORE: <<print $PERM_hiscores[_n].final_score>>
<</for>>

(That's a minimal example to show the logic--you'll probably want to modify it in order to display the scores in a nice table.)

Because you've used <<remember>> rather than <<set>> for the high scores, there's nothing else you need to do in order to save the state of the game.

You can add a link to restart the game like this:

<<link "Restart">>
	<<script>>
		Engine.restart();
	<</script>>
<</link>>

That restarts with no warning as soon as they click the link. If you want to make the player confirm, you can replace Engine.restart(); with UI.restart(); .

by (280 points)
Thank you for that example. Even though I haven't had time to test it yet, it seems to perform pretty much like I wanted it to.

I will have to look closer at the <<remember>> macro though as I didn't know such a thing existed and I want to understand it properly.

My target players are students which all have their own Chromebooks or iPads at the school where I teach. As long as each player can see their best scores on their own machine they can show me their scores AND/OR compare scores with friends sitting next to them. I realize this is saved in the web browser's local storage and might disappear, but I can live with that.
...