Jump to content

Can you get lost in a QR-Code?


---
 Share

Recommended Posts

Sorry in advance for the long post.. I removed a lot of text since it got redonkyless long. So I apologize if some text don't make sense. It mostly consisted of stuff only the geekiest people would appriciate. So hopefully you won't notice it.
But if you have any questions (fun ones) I'll try to answer them if I have time.

When ever I get requests for some out of the box stuff, I always think to my self, I wonder if I could turn this is to that?
And only on very rare occations, I do get time to properly fiddle. Since my idéas seldom go hand
in hand with what I'm supposed to do at my job..

How ever, this time I tought I'd share. And maybe someone will find it useful? Atleat the first half..

So here is a small tutorial of how to make a simple game, out of a "QR-code", with Calypso.
With the sole purpouse: Why not? 😃


So that you can get those boring days to fly by will still being able to say "I'm currently doing wizard
stuff in PCM, shut up!" - To any of your annoying colleauges, bothering you with thier imbecill questions, you already answerd 12 times.

Well let's move on. This might look strange to most, but bare with me, it will make sense in the end. (Hopfully..)
PCM alone can not do what we are about to do. So we need to take adantage of the Smalltalk environment.
(Still not C++) - "Haaa Haaa" - Nelson Muntz


We will make this in a more procedural way, but make no misstakes. This is without any hesitation wrong. And this is NOT
how Smalltalk works. Smalltalk is beautiful. And this stuff below is an total abuse. Almost a crime. Nothing to really mimic.
It's just quick and dirty "playful testing".

Lets start! By settings up some variables. The "walruss" operator is used to assign variables with values.
pov := 0.
playerPos := 25@25.
worldSize := 50.
These we will need on various places further on.

Next, we will create a frame, to host the game. And we store our frame object in gameFrame.
gameFrame := ScheduledWindow model: nil label: 'PCMaze - Navigate with W, A, S, D - Press Q to quit - Made by: E. Moberg.' minimumSize:800@600.
We can try our frame, to see if it works. And we can do like this (Highlight both rows and calculate in some PCM window.):
executeCode("(gameFrame := ScheduledWindow model: nil label: 'PCMaze - Navigate with W, A, S, D - Press Q to quit - Made by: E. Moberg.' minimumSize:800@600) open")
Yeeeeej, we now have our window. But a blank, empty window is't very impressive. We are making a maze game. And you can't make a maze game without a maze...
We define our maze with the integers 0 and 1. Where ever there is a 1, there will be a wall. And we need the outer perimiter of our maze to always
be 1. You can't just walk straight out of our world. Inside on the other hand. Here we can mix.
We wish to do something like this:
1 1 1 1 1            1 1 1 1 1
1       1            1 0 0 0 1
1   1   1            1 0 1 0 1
1       1            1 0 0 0 1
1 1 1 1 1 with this: 1 1 1 1 1
Imagine yourself moving around and around and around until you get bored. And you'll get bored quickly. So we are going to make it 50x50 and totally random generated everytime.
Now the previously created worldSize comes in to play. With the aid of to:do: and block closures, we will iterate 50 times, creating 50 new arrays, that we will store in our first array (maze)
We make another to:do: and a block closure, inside the first, (or a loop in the loop for backwards talking ones)
to fill our new temporary arrays (tempArr) with 0 and 1, but the first and last array must consist of only 1. And so must also the first and last index of the tempArr.
Otherwise we will have holes in the outer perimiter wall. That, we take care of with some boolean conditions. ( | this is the or operator. It also separates
our block closures local variable from our statement.)
[maze := Core.Array new: worldSize. 
1 to: worldSize do: [:i |tempArr := Core.Array new: worldSize. 
	1 to: worldSize do: [:j | block := 1.
		(j = 1)|(j = worldSize)
		ifFalse:[(i = 1)|(i = worldSize)
			ifFalse:[Core.Random new next*1e3>3e2 ifTrue: [block:=0]]].
	tempArr at: j put: block].
maze at: i put: tempArr].
((maze at: playerPos x) at: playerPos y) = 0] whileFalse.
Now we have an array (maze) containing 50 arrays, where every index in maze have another array, 50 integers long. Containing nothing but 0 and 1.
Like this, if it where 3x3: #(#(1 1 1)#(1 0 1)#(1 1 1))
And we will run this until our playerPos (in this case 25@25 isnt a wall (1). We can't spawn in a wall.. doh!)


Now we need to create the main loop, that will keep running our code, over and over again. Until we decide to quit.
We have to be able to quit. So we do that right now. Otherwise testing it will cause issues. So we use the windowManager
to check if we have any keyboard inputs sent to our gameFrame. And if we dont have any inout. We keep looking. And if you press Q. Then we close the gameFrame and terimate everything.
[(gameFrame open) isOpen] whileTrue: [
	[gameFrame windowManager checkForEvents.
	(io := gameFrame windowManager eventQueue next) isKeyboard] whileFalse.
	key := io keyCharacter asString asLowercase. 
	key = 'q'ifTrue:[gameFrame window controller closeAndUnschedule.
		^false].
].
We have our gameFrame. We have our keyboard input Q to close it. And we have our maze. That we can't see.
"Hur kul är det?" As the fat bingolotto lady said in the nineties.. So it's time to make it visible.
In our main loop. We add some objects that need. Here I'm not really sure.
But I think everything that we abandon after one iteration will be taken care of by the garbage collector.
But in all honesty, I can't really figure out how the visualworks GC works.... And it doesn't really matter.
This is just for fun, so who cares if we cause memory leaks after 95 thousand iterations?
	cP:=CompositePart new. 
	gA:=GraphicsAttributes.
	gAW:=GraphicsAttributesWrapper. 
And we draw our maze in the game frame. And once its drawn, we send that to our gameFrame component. And finally we refresh the frame to behold our mighty maze.
	1 to: worldSize do: [
		:i | 1 to: worldSize do: [
			:j | ((maze at: i) at: j) = 1 ifTrue: [
				cP add:((gAW on: ((j-1*10)@(i-1*10 )extent: 10@(10))asFiller)attributes:(gA new paint:(ColorValue black)))]]].
	gameFrame component:(BoundedWrapper on: cP).
	gameFrame refresh.
If you have done everthing correct. You have code that looks like this:
pov := 0.
playerPos := 25@25.
worldSize := 50.
gameFrame := ScheduledWindow model: nil label: 'PCMaze - Navigate with W, A, S, D - Press Q to quit - Made by: E. Moberg.' minimumSize:800@600.
[maze := Core.Array new: worldSize. 
1 to: worldSize do: [:i |tempArr := Core.Array new: worldSize. 
	1 to: worldSize do: [:j | block := 1.
		(j = 1)|(j = worldSize)
		ifFalse:[(i = 1)|(i = worldSize)
			ifFalse:[Core.Random new next*1e3>3e2 ifTrue: [block:=0]]].
	tempArr at: j put: block].
maze at: i put: tempArr].
((maze at: playerPos x) at: playerPos y) = 0] whileFalse.

[(gameFrame open) isOpen] whileTrue: [

	cP:=CompositePart new. 
	gA:=GraphicsAttributes.
	gAW:=GraphicsAttributesWrapper. 
	1 to: worldSize do: [:i | 1 to: worldSize do: [:j | ((maze at: i) at: j) = 1 ifTrue: [cP add:((gAW on:((j-1*10)@(i-1*10)extent:10@(10))asFiller)attributes:(gA new paint:(ColorValue black)))]]].

	gameFrame component:(BoundedWrapper on: cP).
	gameFrame refresh.
	[gameFrame windowManager checkForEvents.
	(io := gameFrame windowManager eventQueue next) isKeyboard] whileFalse.
	key := io keyCharacter asString asLowercase. 
	key = 'q'ifTrue:[gameFrame window controller closeAndUnschedule.
		^false].
].
Lets take a sneek peak at what we have done so far. (You don't have to type this, its the same code as above. But in a line, and formatted to be used with executeCode().
And it's quite hard to fully grap it, when written like this. Thats the reason for the more readable code...)
Again, highlight and compute in a PCM window.
executeCode("gameFrame := ScheduledWindow model: nil label: 'PCMaze - Navigate with W, A, S, D - Press Q to quit - Made by: E. Moberg.' minimumSize:800@600. playerPos := 25@25. worldSize := 50. [maze := Core.Array new: worldSize. 1 to: worldSize do: [:i |tempArr := Core.Array new: worldSize. 1 to: worldSize do: [:j | block := 1. (j = 1)|(j = worldSize) ifFalse:[(i = 1)|(i = worldSize) ifFalse:[Core.Random new next*1e3>3e2 ifTrue: [block:=0]]]. tempArr at: j put: block]. maze at: i put: tempArr]. ((maze at: playerPos x) at: playerPos y) = 0] whileFalse. gameFrame := ScheduledWindow model: nil label: 'PCMaze - Navigate with W, A, S, D - Press Q to quit - Made by: E. Moberg.' minimumSize:800@600. [(gameFrame open) isOpen] whileTrue: [cP:=CompositePart new. gA:=GraphicsAttributes. gAW:=GraphicsAttributesWrapper. 1 to: worldSize do: [:i | 1 to: worldSize do: [:j | ((maze at: i) at: j) = 1 ifTrue: [cP add:((gAW on:((j-1*10)@(i-1*10)extent:10@(10))asFiller)attributes:(gA new paint:(ColorValue black)))]]]. gameFrame component:(BoundedWrapper on: cP). gameFrame refresh. [gameFrame windowManager checkForEvents. (io := gameFrame windowManager eventQueue next) isKeyboard] whileFalse. key := io keyCharacter asString asLowercase. key = 'q'ifTrue:[gameFrame window controller closeAndUnschedule. ^false].].")
Now this looks like a QR-code, that you can get lost in, and this was the moment where my eyes rolled upwards, and my mind said "I wonder if..." some years ago.
This is also the end of the useful stuff. So if you are a serious blue collar, working class hero. Then go back to work...
Now, this is how I generated the QR-codes. But it also converted info to binary, took care of code orientation, timing, format info etc, etc.
So don't be fooled, your not at the finishline of setting up your own QR-workshop.

And now might be a good time for some background to all of this.
Some years ago I was asked if Calypso could generate info about the results in a qr-code.
# of out of tol, # of characteristics, etc. etc... That you can print out as a sticker and put on the part.
And I always answer, Hell yeah it can. NO PROBLEM! Then, some weeks later, when they asked what happend to the qr-codes. I asked if they where serious?
Since I obviously had totally forgot about it, and only tought it was one of those a hypothetical questions. Not one that I now had to solve since I
never know when to just staring with a dead eye and my mouth open and look stupid, instead of saying yes to everything. Well, I got in a rush to backup my words.
And had no time to play with it back then. How ever, the important part: I did it. It worked, it was awsome. But it died as quick as it was brought alive. And was never used.
I never got to know why, but I'll bet some operator had issues figuring out how to put the stickers on smaller parts. They where simply not smart enough to put the sticker on a bag and place the part inside of it.
Anyhow, when I saw the same output that you now are looking at now. It gave me a flashback to the 90's, and Wolfenstein 3D, when we all did our own raycaster games. One more terrible then the other, and shared them like STD's on 3.5 inch discs in school. Aaaa those where the days........
So this, along with pretty much else with potential for fun ended up on my "investigate this" list. And finally, during the holidays, I got some paid spare time, to look in to it.

Well, lets move on. This QR-looking thing isn't of much joy to the eye. So let's remove it. No one who kept on reading really cares about actual work stuff, right?
Remove this line: (1 to: worldSize do:[....") And replace it with this:
	0 to: 80 do: [:i | ray := pov + (i-40) asRAD. 					"loop thru our field of view, divided in ray angles"
		x := playerPos x. 							"Our X position, we need this to know from where to cast our rays"			
		y := playerPos y. 							"Our Y position"
		n := 0.									"Just a counter, to keep track of the distance.
		[x := x + (2e-2 * ray cos).						"Here we create another loop, where our ray is checking for wall collision"
			y := y + (2e-2 * ray sin).					"Same but i Y."
			n := n + 1.							"Increment to keep track of distance"
			((maze at: x rounded) at: y rounded) = 1] whileFalse. 		"Check if the position of the ray is a wall. Eg. 1?) If not, cast the ray further and check again"
		h := 1 / (35e-5 * (n * (pov - ray) cos)).				"Now we have hit something. So here we calculate what height the wall sould have, based on the distance (n)"
		s := (1 - (0.1 / h) / 2) asString.					"Shading"
		s := ((s at: 5) asString , (s at: 6) asString) asFloat * 1e-2 / 1.1.	"RGB value for the shading. This is buttugly, but short :)"
		n > 570 ifTrue: [s := 0].						"Limit the shading, so it can't be white at long distances"
		cP add: ((gAW on: ((i-1*10)@(200-h) extent: 10@(h*5)) asFiller) attributes: (gA new paint: (ColorValue red: s green: s blue: s)))].	"Add the rectangle with the values we have calculated above"
We are going to keep rendering these rectangels. But verticaly, in our whole field of view, from a perspective like we where standing in the middle of it. Not looking from above. Like we just did previously.
So here, we cast rays within our feild of view. The ray will travel in our maze, and if it hits a 1, we will render that as a wall. If not, it will move on until it does. When it hits a wall, we calculate the distance to the impact,
and scale it. We also subtract the angle between our point of view, and the ray. Otherwise it will look like you have a fish bowl on your head.
We also use the distance, to shade the walls, to give false sence of depth. I have left small comments on a row by row basis, so it's easier to follow.
If you mock this up your self. You might stare in to a wall like a stupid. So we must be able to move.
So we jump down to the bottom, and add movement to our player. We use W, S, A & D. And once we have hit a key, we update the player position. If we don't try to walk thru a wall.
	key= 'w' ifTrue: [x := playerPos x + (pov cos * 0.1).
		y := playerPos y + (pov sin * 0.1)].
	key= 's' ifTrue: [x := playerPos x - (pov cos * 0.1).
		y := playerPos y - (pov sin * 0.1)].
	key= 'a' ifTrue: [pov := pov - 0.3927].
	key= 'd' ifTrue: [pov := pov + 0.3927].
	((maze at:x rounded)at:y rounded)=0
		ifTrue:[playerPos:=x@y].
].
And then we just add color to the sky and another color to the floor. And there we have it. A QR-code you can get lost in. Here is for those lazy people among you:
(This has gone thru some wieght loss, but it's the same code as above, +sky and floor.)
executeCode("v:=0.c:=25@25.k:=50.[m:=Core.Array new:k. 1 to:k do:[:j|l:=Core.Array new:k. 1 to:k do:[:i|b:=1.(i=1)|(i=k)ifFalse:[(j=1)|(j=k)ifFalse:[Core.Random new next*1e3>3e2ifTrue:[b:=0]]].l at:i put:b].m at:j put:l].((m at:c x)at:c y)=0]whileFalse. f:=ScheduledWindow model:nil label:'PCMaze - Navigate with W, A, S, D - Press Q to quit - Made by: E. Moberg.'minimumSize:800@600.[(f open)isOpen]whileTrue:[u:=CompositePart new. o:=GraphicsAttributes. p:=GraphicsAttributesWrapper. u add:((p on:(0@0 extent:8e2@2e2)asFiller)attributes:(o new paint:ColorValue royalBlue)).u add:((p on:(0@2e2 extent:8e2@4e2)asFiller)attributes:(o new paint:ColorValue veryDarkGray)).0 to:80 do:[:i|b:=v+(i-40)asRAD. x:=c x. y:=c y. n:=0.[x:=x+(2e-2*b cos).y:=y+(2e-2*b sin).n:=n+1.((m at:x rounded)at:y rounded)=1]whileFalse. h:=1/(35e-5*(n*(v-b)cos)).s:=(1-(0.1/h)/2)asString. s:=((s at:5)asString,(s at:6)asString)asFloat*1e-2/1.1.n>550ifTrue:[s:=0].u add:((p on:((i-1*10)@(2e2-h)extent:10@(h*5))asFiller)attributes:(o new paint:(ColorValue red:s green:s blue:s)))].f component:(BoundedWrapper on:u).f refresh.[f windowManager checkForEvents.(u:=f windowManager eventQueue next)isKeyboard]whileFalse. o:=u keyCharacter asString asLowercase. o='w'ifTrue:[x:=c x+(v cos*0.1).y:=c y+(v sin*0.1)].o='s'ifTrue:[x:=c x-(v cos*0.1).y:=c y-(v sin*0.1)].o='a'ifTrue:[v:=v-0.3927].o='d'ifTrue:[v:=v+0.3927].o='q'ifTrue:[f window controller closeAndUnschedule.^false].((m at:x rounded)at:y rounded)=0ifTrue:[c:=x@y]].")
We are now walking around inside of the QR-code. And if sort of looks like 3d. But all it is, is rectangular shapes in different lenghts, and scales of gray.
Now, you can bind more keys to change the scales, making the walls taller or shorter. Or add more objects, like a masterprobe that you can run around looking for.
Or add our first output as a mini-map in the corner. So that you can see where you are. Add your self on the map, as a red dot (as in the picture), etc, etc.
It's yours to play around with. But if you do something funny, share it. Don't be duche...
Have fun, and I hope you learn something along the way. 😃

For those of you that don't have PCM, here is a picture of what the rest of us get to play with. So whip up your purse, pay for a license. And join the cool people! 114_ff753abe4726d9fb3f4a91dd7de5ad32.png
Link to comment
Share on other sites

Please sign in to view this quote.

Sometimes I have the impression that you left some Typo to make our lives more miserable:

cP add:((gAW on: (playerPos x@playerPos y extent: 5@5)asFiller)attributes:(gA new paint:ColorValue red)).
Link to comment
Share on other sites

  • 10 months later...

Please sign in to view this quote.

run itself end sup in error since there are no characteristics,
I executed the feature and maze showed up and it is fun and you DO get lost 😃

however, in Eric original post there was an actual map on top right corner _actual QR code and a red dot_
this is what I meant with cant find the map

also was going to ask you if there is anything in there to be found? an objective to game?

regardless, I know it was all about the code and not the game and i thank you and eric for education
Link to comment
Share on other sites

Please sign in to view this quote.

Oh, sorry, I misunderstood.

I think you'll have do add it for yourself :)

Eric used one function here that should NEVER be used in metrology :)
Link to comment
Share on other sites

Please sign in to view this quote.

Hey I was wrong. Eric is still patching Calypso and posting videos about it on his YT channel.

Eric: are you patching like hotfix approach?
Link to comment
Share on other sites

 Share

×
×
  • Create New...