Author Topic: Association Game  (Read 4752 times)

0 Members and 1 Guest are viewing this topic.

This topic contains a post which is marked as Best Answer. Press here if you would like to see it.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Association Game
« on: November 09, 2020, 10:28:35 pm »
I saw folks playing around with a "Guess the Animal" game, so thought I'd play around with something similar, which learns as it goes.

The concept is this one:  You basically give it a NAME of something, and then some DESCRIPTORS for that something.  The program will then let you guess something, and try and associate those descriptors with that name, to see if it can find what you're thinking of.

For example, I give it the following data NAME - DESCRIPTOR:

Steve -- fat, sexy, bald
Pete -- old, stinky, Republican
Bplus -- codes too much, is less than an A, always on [abandoned, outdated and now likely malicious qb64 dot net website - don’t go there]
Luke -- old, fat, codes too much

It'll then ask you a series of questions such as:

Is what you're thinking of old?

If you say yes, it eliminates Steve and Bplus from the possible answers, and then it eliminates "old" from the set of possible questions to ask.  At this point, it then can ask, "Is what you're thinking of stinky?"  If the answer is yes, it'll guess Pete as he's the only option left.  If the answer is no, it'll guess Luke, as he's the only option left.  If neither guess is correct, it'll ask what you were thinking of and ask for you to add new descriptors to it, so it can continue learning...

Anyway, what I have so far is this little bit (it's getting bed time here tonight, but I thought I'd share to kinda showcase the basic idea behind the concept, and to allow other folks a chance to get started on something similar of their own, to see how they'd go about accomplishing this little task.  Bplus likes these type of challenges; maybe he'll want to give us a version of his own, so I'll have someone to compete and compare with, if nobody else is willing to give this little project a shot in the dark.  Remember: there's no failure in failing.  You can only fail by not trying.  ;D

Code: QB64: [Select]
  1. REDIM SHARED Nam(0) AS STRING, Des(0) AS STRING 'Names and Descriptions
  2. DIM SHARED NoN AS LONG, NoD AS LONG 'Number of Names and Descriptions
  3. DIM SHARED LoS AS LONG 'Length of String
  4.  
  5.  
  6. OPEN "Learning Program.txt" FOR BINARY AS #1
  7. IF LOF(1) = 0 THEN 'it's a blank file
  8.     PUT #1, 1, NoN 'put 0's in the file
  9.     PUT #1, 5, NoD
  10.     GET #1, 1, NoN 'else get the number of names and descriptions
  11.     GET #1, 5, NoD
  12.  
  13.  
  14. REDIM SHARED Nam(NoN) AS STRING, Des(NoD) AS STRING
  15. FOR i = 1 TO NoN
  16.     GET #1, , LoS
  17.     t$ = SPACE$(LoS)
  18.     GET #1, , t$
  19.     Nam(i) = t$ 'This should just be a name of something, like Steve, or Steve's car
  20. FOR i = 1 TO NoD
  21.     GET #1, , LoS
  22.     t$ = SPACE$(LoS)
  23.     GET #1, , t$
  24.     Nam(i) = t$ 'This should be a descrition and numbers that it's associated with, such as "Fat" + CHR$(0) + " 1  3  5 ".  This tells us that NAMES 1, 3, and 5 are all fat.
  25. CLOSE 'And we're finished with disk access, for now.
  26.  
  27. SCREEN _NEWIMAGE(800, 600, 32)
  28.  
  29.     CLS , SkyBlue
  30.     PrintTotals
  31.     IF NoN = 0 THEN
  32.         GetNewThing
  33.     ELSE
  34.         LetMeGuess
  35.     END IF
  36.     _LIMIT 30
  37. LOOP UNTIL finished
  38.  
  39.  
  40.  
  41. SUB PrintTotals
  42.     COLOR White: PRINT "I know";
  43.     COLOR Yellow: PRINT NoN;
  44.     COLOR White: PRINT "names, and ";
  45.     COLOR Yellow: PRINT NoD;
  46.     COLOR White: PRINT "descriptions."
  47.  
  48.  
  49. SUB LetMeGuess
  50.  
  51.     PRINT
  52.     PRINT "Since I know stuff, you think of something, and I'll try and guess at what you're thinking of!"
  53.     REDIM PA(NoN) AS LONG 'possible answers
  54.     FOR i = 1 TO NoN: PA(i) = i: NEXT 'corresponding to all options starting out
  55.     REDIM PQ(NoD) AS LONG 'possible questions/descriptors
  56.     FOR i = 1 TO NoD: PQ(i) = i: NEXT 'corresponding to all possible descriptors starting out
  57.     DO
  58.         IF UBOUND(PA) = 1 THEN 'there's only one possible answer
  59.             finished = -1
  60.             PRINT "Is what you're thinking of "; Nam(PA(1)); "?";
  61.             DO: i$ = UCASE$(INPUT$(1)): LOOP UNTIL i$ = "Y" OR i$ = "N"
  62.             IF i$ = "Y" THEN
  63.                 BEEP
  64.                 BEEP
  65.                 PRINT "I WON!  CHEESY NON-ENDING TO GAME!"
  66.                 END
  67.             ELSE
  68.                 GetNewThing 'if we didn't guess the right thing, then the user needs to give us a new thing!
  69.             END IF
  70.         ELSE
  71.             IF UBOUND(PQ) > 0 THEN
  72.                 guess = INT(RND * UBOUND(PQ)) + 1
  73.                 t$ = Des(PQ(guess)) 'our guess data line
  74.                 l = INSTR(t$, CHR$(0)) 'with the breakpoint between descriptor and entry values at point l
  75.                 d$ = LEFT$(t$, l - 1) 'the descriptor itself
  76.                 t$ = MID$(t$, l + 1) 'all the records which reference this descriptor
  77.                 PRINT "Is what you're thinking of "; d$; "?";
  78.                 DO: i$ = UCASE$(INPUT$(1)): LOOP UNTIL i$ = "Y" OR i$ = "N"
  79.                 IF i$ = "Y" THEN 'it is one of these type items.  Eliminate anything without this particular tag.
  80.                     FOR i = 1 TO UBOUND(PQ)
  81.                         s$ = " " + STR$(PQ(i)) + " "
  82.                         IF INSTR(t$, s$) = 0 THEN PQ(i) = 0 'remove all questions which don't reference this tag.
  83.                     NEXT
  84.                     FOR i = 1 TO UBOUND(PA)
  85.                         s$ = " " + STR$(PA(i)) + " "
  86.                         IF INSTR(t$, s$) = 0 THEN PQ(i) = 0 'remove all answers which don't reference this tag.
  87.                     NEXT
  88.                     PQ(guess) = 0 'remove this question from our line up of future possible questions.
  89.                 ELSE 'it's not one of these type items.  Eliminate everything that holds this tag.
  90.  
  91.                 END IF
  92.                 PA = 0: pq = 0
  93.                 FOR i = 1 TO UBOUND(PA)
  94.                     IF PA(i) <> 0 THEN PA = PA + 1: PA(PA) = PA(i): PRINT i, "Possible Answer:"; PA(i)
  95.                 NEXT
  96.                 REDIM _PRESERVE PA(PA) AS LONG
  97.                 FOR i = 1 TO UBOUND(pq)
  98.                     IF PQ(i) <> 0 THEN pq = pq + 1: PQ(pq) = PQ(i): PRINT i, pq, "Possible Question:"; Des(PQ(i))
  99.                 NEXT
  100.                 REDIM _PRESERVE PQ(pq) AS LONG
  101.  
  102.             ELSE
  103.  
  104.             END IF
  105.  
  106.         END IF
  107.     LOOP UNTIL finished
  108.  
  109.  
  110. SUB GetNewThing
  111.     PRINT
  112.     INPUT "What do you call this marvelous new thing that I know nothing about =>"; n$
  113.     n$ = _TRIM$(n$)
  114.     IF n$ = "" THEN
  115.         'ShutDown 'not implemented yet, but here's where we could end the program, if we want, just by leaving things blank.
  116.         SYSTEM 'this should be an EXIT SUB, once ShutDown is implemented.
  117.     END IF
  118.     FOR i = 1 TO NoN
  119.         IF _STRICMP(Nam(i), n$) = 0 THEN EXIT FOR
  120.     NEXT
  121.     IF i <= NoN THEN 'we already have such a thing
  122.         PRINT
  123.         PRINT "You've already told me about "; n$; ".  ";
  124.         t$ = " " + STR$(i) + " "
  125.         FOR j = 1 TO NoD
  126.             IF INSTR(Des(j), t$) THEN out$ = out$ + LEFT$(Des(j), INSTR(Des(j), CHR$(0)) - 1) + ","
  127.         NEXT
  128.         out$ = "So far, I know it's " + LEFT$(out$, LEN(out$) - 1) + "."
  129.     ELSE 'otherwise, it's something new to learn about
  130.         out$ = "So far, I don't know anything about this " + n$ + "."
  131.         NoN = NoN + 1
  132.         REDIM _PRESERVE Nam(NoN) AS STRING
  133.         Nam(NoN) = n$
  134.     END IF
  135.     index = i 'this is the index reference of the name we're working with
  136.     PRINT out$
  137.     PRINT
  138.     PRINT "Tell me three new things which describe this "; n$
  139.     FOR i = 1 TO 3
  140.         LearnNewThing:
  141.         PRINT "Thing #" + _TRIM$(STR$(i)) + "=>";
  142.         INPUT " "; t$ 'learn something about this thing
  143.         t$ = _TRIM$(t$)
  144.         'check to see if the thing we're learning is already a listed trait
  145.         FOR j = 1 TO NoD
  146.             t1$ = LEFT$(Des(j), INSTR(Des(j), CHR$(0)) - 1)
  147.             IF _STRICMP(t$, t1$) = 0 THEN EXIT FOR 'yep, it already exists.  No need to look further
  148.         NEXT
  149.         IF j <= NoD THEN 'we found an existing item
  150.             t1$ = " " + STR$(index) + " "
  151.             IF INSTR(Des(j), t1$) THEN 'we already have this fact associated with this descriptor
  152.                 COLOR Red
  153.                 PRINT "Sorry.  I already knew this about "; n$; ".  Tell me some new thing about it, instead."
  154.                 COLOR White
  155.                 GOTO LearnNewThing
  156.             ELSE
  157.                 Des(j) = Des(j) + " " + STR$(index) + " " 'add this fact to this descriptor
  158.             END IF
  159.         ELSE 'we're learning a completely new descriptor
  160.             NoD = NoD + 1
  161.             REDIM _PRESERVE Des(NoD) AS STRING
  162.             Des(NoD) = t$ + CHR$(0) + " " + STR$(index) + " "
  163.         END IF
  164.     NEXT
  165.  

Note that this is a barely viable work-in-progress at this stage.  It can open and read a file, but it can't write to one.  There's no shutdown routine.  There's a lot of unwritten blocks of ELSE, that simple follow up with END IF... 

The plan (for me, at least), is to fill in those missing blocks tomorrow, and see about making this a working little project where it'll learn as it goes, so it can figure out how to associate various descriptors to corresponding items.



UNRELATED Note2:  This is also similar to how I'd end up writing a learning chat bot, which is what I once tried to explain to Ron about.  You basically form a list of nouns, verbs, and adjectives, and then you form these exact style associations between them.  Then when the user triggers one of those items, you can flow the conversation to one of the matching associations.

User:  I like cars.
Chat Bot:  Some cars are green.  (green is associated as an attribute with cars)
User: Yeah, but some are blue.
Chat Bot: The sky is blue.  (the sky is associated as something having an attribute as blue)
« Last Edit: November 10, 2020, 12:20:24 pm by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SpriggsySpriggs

  • Forum Resident
  • Posts: 1145
  • Larger than life
    • View Profile
    • GitHub
Re: Association (WIP)
« Reply #1 on: November 09, 2020, 10:38:24 pm »
This sounds like Akinator. Give it a search on Google
Shuwatch!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Association (WIP)
« Reply #2 on: November 09, 2020, 10:54:57 pm »
This sounds like Akinator. Give it a search on Google

From my little test run of it, it seems like a very similar concept.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Association (WIP)
« Reply #3 on: November 09, 2020, 11:27:16 pm »
From Guess Animal, I was thinking of a series of yes, no questions and for each question a 0 or 1 assigned so each object's "DNA" is a series of answers to the yes/no question in that questions nth position of n total questions.

Not that different from pizza order numbers whether you want topping number n or not.

Trick is to get smallest amount of questions to cover greatest amount of objects so each can be uniquely ID'd.

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: Association (WIP)
« Reply #4 on: November 10, 2020, 03:31:46 am »
Man-u-er crazy, and I think it's you're idea that stinks, but apparently I'm too nose blind to make that assessment.

Pete
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Association (WIP)
« Reply #5 on: November 10, 2020, 11:00:55 am »
That description, that makes you a lot of friends ... hahaha :)

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Association (WIP)
« Reply #6 on: November 10, 2020, 11:34:44 am »
Code: QB64: [Select]
  1. REDIM SHARED Nam(0) AS STRING, Des(0) AS STRING 'Names and Descriptions
  2. DIM SHARED NoN AS LONG, NoD AS LONG 'Number of Names and Descriptions
  3. DIM SHARED LoS AS LONG 'Length of String
  4.  
  5.  
  6. OPEN "Learning Program.txt" FOR BINARY AS #1
  7. IF LOF(1) = 0 THEN 'it's a blank file
  8.     PUT #1, 1, NoN 'put 0's in the file
  9.     PUT #1, 5, NoD
  10.     GET #1, 1, NoN 'else get the number of names and descriptions
  11.     GET #1, 5, NoD
  12.  
  13.  
  14. REDIM SHARED Nam(NoN) AS STRING, Des(NoD) AS STRING
  15. FOR i = 1 TO NoN
  16.     GET #1, , LoS
  17.     t$ = SPACE$(LoS)
  18.     GET #1, , t$
  19.     Nam(i) = t$ 'This should just be a name of something, like Steve, or Steve's car
  20. FOR i = 1 TO NoD
  21.     GET #1, , LoS
  22.     t$ = SPACE$(LoS)
  23.     GET #1, , t$
  24.     Des(i) = t$ 'This should be a descrition and numbers that it's associated with, such as "Fat" + CHR$(0) + " 1  3  5 ".  This tells us that NAMES 1, 3, and 5 are all fat.
  25. CLOSE 'And we're finished with disk access, for now.
  26.  
  27. SCREEN _NEWIMAGE(800, 600, 32)
  28.  
  29.     CLS , SkyBlue
  30.     PrintTotals
  31.     IF NoN = 0 THEN
  32.         GetNewThing
  33.     ELSE
  34.         LetMeGuess
  35.     END IF
  36.     IF _EXIT THEN ShutDown 'if the user clicks the little red X in the corner of the program window, save our data files before ending
  37.     _LIMIT 30
  38.  
  39.  
  40.  
  41. SUB PrintTotals
  42.     COLOR White: PRINT "I know";
  43.     COLOR Yellow: PRINT NoN;
  44.     COLOR White: PRINT "names, and ";
  45.     COLOR Yellow: PRINT NoD;
  46.     COLOR White: PRINT "descriptions."
  47.  
  48.  
  49. SUB LetMeGuess
  50.  
  51.     PRINT
  52.     PRINT "Since I know stuff, you think of something, and I'll try and guess at what you're thinking of!"
  53.     REDIM PA(NoN) AS LONG 'possible answers
  54.     FOR i = 1 TO NoN: PA(i) = i: NEXT 'corresponding to all options starting out
  55.     REDIM PQ(NoD) AS LONG 'possible questions/descriptors
  56.     FOR i = 1 TO NoD: PQ(i) = i: NEXT 'corresponding to all possible descriptors starting out
  57.     DO
  58.         IF UBOUND(PA) = 1 THEN 'there's only one possible answer
  59.             finished = -1
  60.             PRINT "Is what you're thinking of "; Nam(PA(1)); "?";
  61.             DO: i$ = UCASE$(INPUT$(1)): LOOP UNTIL i$ = "Y" OR i$ = "N"
  62.             IF i$ = "Y" THEN
  63.                 Gloat
  64.                 finished = -1
  65.             ELSE
  66.                 GetNewThing 'if we didn't guess the right thing, then the user needs to give us a new thing!
  67.             END IF
  68.         ELSEIF UBOUND(PA) = 0 THEN
  69.             GetNewThing 'no possible answers left, so no matter how many questions we ask, we can't give an answer to the game.
  70.             'May as well give up at this point and ask for the new thing.
  71.             finished = -1
  72.         ELSE
  73.             IF UBOUND(PQ) > 0 THEN 'As long as we have questions to guess, we can keep playing the game.
  74.                 guess = INT(RND * UBOUND(PQ)) + 1
  75.                 t$ = Des(PQ(guess)) 'our guess data line
  76.                 l = INSTR(t$, CHR$(0)) 'with the breakpoint between descriptor and entry values at point l
  77.                 d$ = LEFT$(t$, l - 1) 'the descriptor itself
  78.                 t$ = MID$(t$, l + 1) 'all the records which reference this descriptor
  79.                 PRINT "Is what you're thinking of "; d$; "?";
  80.                 DO: i$ = UCASE$(INPUT$(1)): LOOP UNTIL i$ = "Y" OR i$ = "N"
  81.  
  82.                 PQ(guess) = 0 'remove this question from our line up of future possible questions.
  83.                 IF i$ = "Y" THEN 'it is one of these type items.  Eliminate anything without this particular tag.
  84.                     FOR i = 1 TO UBOUND(PQ)
  85.                         s$ = " " + STR$(PQ(i)) + " "
  86.                         IF INSTR(t$, s$) = 0 THEN PQ(i) = 0 'remove all questions which don't reference this tag.
  87.                     NEXT
  88.                     FOR i = 1 TO UBOUND(PA)
  89.                         s$ = " " + STR$(PA(i)) + " "
  90.                         IF INSTR(t$, s$) = 0 THEN PQ(i) = 0 'remove all answers which don't reference this tag.
  91.                     NEXT
  92.                 ELSE 'it's not one of these type items.  Eliminate everything that holds this tag.
  93.                     'FOR i = 1 TO UBOUND(PQ)
  94.                     '    s$ = " " + STR$(PQ(i)) + " "
  95.                     '    IF INSTR(t$, s$) <> 0 THEN PQ(i) = 0 'remove all questions which reference this tag.
  96.                     'NEXT
  97.                     FOR i = 1 TO UBOUND(PA)
  98.                         s$ = " " + STR$(PA(i)) + " "
  99.                         IF INSTR(t$, s$) <> 0 THEN PQ(i) = 0 'remove all answers which reference this tag.
  100.                     NEXT
  101.                 END IF
  102.                 'Look at our remaining answers and eliminate any questions which don't relate to one of them
  103.                 FOR i = 1 TO UBOUND(PQ)
  104.                     IF PQ(i) <> 0 THEN
  105.                         t$ = Des(PQ(i)) 'our guess data line
  106.                         l = INSTR(t$, CHR$(0)) 'with the breakpoint between descriptor and entry values at point l
  107.                         q$ = " " + LEFT$(t$, l - 1) + " " 'the question/descriptor itself
  108.                         FOR j = 1 TO UBOUND(PA)
  109.                             s$ = " " + STR$(PA(j)) + " " 'the search string
  110.                             IF INSTR(q$, s$) <> 0 THEN EXIT FOR
  111.                         NEXT
  112.                         IF j > UBOUND(PA) THEN PQ(i) = 0 'if there's no answers left which matches this question, toss it out
  113.                     END IF
  114.                 NEXT
  115.  
  116.  
  117.  
  118.                 pa = 0: pq = 0
  119.                 FOR i = 1 TO UBOUND(pa)
  120.                     IF PA(i) <> 0 THEN pa = pa + 1: PA(pa) = PA(i)
  121.                 NEXT
  122.                 FOR i = 1 TO UBOUND(pq)
  123.                     IF PQ(i) <> 0 THEN pq = pq + 1: PQ(pq) = PQ(i)
  124.                 NEXT
  125.                 REDIM _PRESERVE PA(pa) AS LONG: REDIM _PRESERVE PQ(pq) AS LONG
  126.             ELSE
  127.                 'At this point, we've ran out of questions.
  128.                 IF UBOUND(pa) > 0 THEN 'If there's any answers left, let's just take a shot in the dark that it might be one of them.
  129.                     guess = INT(RND * UBOUND(pa)) + 1
  130.                     PRINT "Is what you're thinking of "; Nam(PA(guess)); "?";
  131.                     DO: i$ = UCASE$(INPUT$(1)): LOOP UNTIL i$ = "Y" OR i$ = "N"
  132.                     IF i$ = "Y" THEN Gloat ELSE GetNewThing 'if we didn't guess the right thing, then the user needs to give us a new thing!
  133.                 ELSE
  134.                     GetNewThing 'We don't have any possible questions left, nor any possible answers to give.
  135.                     'We're completely stumped at this point, and we need to get this marvelous new thing so we can learn more about it!
  136.                 END IF
  137.                 finished = -1
  138.             END IF
  139.         END IF
  140.         CLS , SkyBlue
  141.     LOOP UNTIL finished
  142.  
  143.  
  144. SUB ShutDown
  145.     OPEN "Learning Program.txt" FOR OUTPUT AS #1: CLOSE #1 'kill the old data file
  146.     OPEN "Learning Program.txt" FOR BINARY AS #1
  147.  
  148.     PUT #1, 1, NoN 'put number of names in file
  149.     PUT #1, 5, NoD 'put number of descriptions in file
  150.  
  151.     FOR i = 1 TO NoN
  152.         LoS = LEN(Nam(i))
  153.         t$ = SPACE$(LoS)
  154.         PUT #1, , LoS 'put the length of the name to file
  155.         PUT #1, , Nam(i) 'put the name to file
  156.     NEXT
  157.     FOR i = 1 TO NoD
  158.         LoS = LEN(Des(i))
  159.         t$ = SPACE$(LoS)
  160.         PUT #1, , LoS 'put the length of the name to file
  161.         PUT #1, , Des(i) 'put the description to file
  162.     NEXT
  163.     CLOSE 'And we're finished with disk access, for now.Stev
  164.     SYSTEM 'And we're finished with the program as well!
  165.  
  166.  
  167. SUB GetNewThing
  168.     PRINT
  169.     INPUT "What do you call this marvelous new thing that I know nothing about =>"; n$
  170.     n$ = _TRIM$(n$)
  171.     IF n$ = "" THEN ShutDown
  172.     FOR i = 1 TO NoN
  173.         IF _STRICMP(Nam(i), n$) = 0 THEN EXIT FOR
  174.     NEXT
  175.     IF i <= NoN THEN 'we already have such a thing
  176.         PRINT
  177.         PRINT "You've already told me about "; n$; ".  ";
  178.         t$ = " " + STR$(i) + " "
  179.         FOR j = 1 TO NoD
  180.             IF INSTR(Des(j), t$) THEN OUT$ = OUT$ + LEFT$(Des(j), INSTR(Des(j), CHR$(0)) - 1) + ","
  181.         NEXT
  182.         OUT$ = "So far, I know it's " + LEFT$(OUT$, LEN(OUT$) - 1) + "."
  183.     ELSE 'otherwise, it's something new to learn about
  184.         OUT$ = "So far, I don't know anything about this " + n$ + "."
  185.         NoN = NoN + 1
  186.         REDIM _PRESERVE Nam(NoN) AS STRING
  187.         Nam(NoN) = n$
  188.     END IF
  189.     index = i 'this is the index reference of the name we're working with
  190.     PRINT OUT$
  191.     PRINT
  192.     PRINT "Tell me three new things which describe this "; n$
  193.     FOR i = 1 TO 3
  194.         LearnNewThing:
  195.         PRINT "Thing #" + _TRIM$(STR$(i)) + "=>";
  196.         INPUT " "; t$ 'learn something about this thing
  197.         t$ = _TRIM$(t$)
  198.         'check to see if the thing we're learning is already a listed trait
  199.         FOR j = 1 TO NoD
  200.             t1$ = LEFT$(Des(j), INSTR(Des(j), CHR$(0)) - 1)
  201.             IF _STRICMP(t$, t1$) = 0 THEN EXIT FOR 'yep, it already exists.  No need to look further
  202.         NEXT
  203.         IF j <= NoD THEN 'we found an existing item
  204.             t1$ = " " + STR$(index) + " "
  205.             IF INSTR(Des(j), t1$) THEN 'we already have this fact associated with this descriptor
  206.                 COLOR Red
  207.                 PRINT "Sorry.  I already knew this about "; n$; ".  Tell me some new thing about it, instead."
  208.                 COLOR White
  209.                 GOTO LearnNewThing
  210.             ELSE
  211.                 Des(j) = Des(j) + " " + STR$(index) + " " 'add this fact to this descriptor
  212.             END IF
  213.         ELSE 'we're learning a completely new descriptor
  214.             NoD = NoD + 1
  215.             REDIM _PRESERVE Des(NoD) AS STRING
  216.             Des(NoD) = t$ + CHR$(0) + " " + STR$(index) + " "
  217.         END IF
  218.     NEXT
  219.  
  220. SUB Gloat
  221.     BEEP
  222.     BEEP
  223.     PRINT
  224.     PRINT
  225.     PRINT "I WON!  I'M SMARTER THAN YOU ARE!  I'VE LEARNED A LOT!!"
  226.     BEEP
  227.     BEEP
  228.     _DELAY 3
  229.  

It's almost working.  Only glitch I see is that sometimes I'm eliminating things to guess at which haven't truly been ruled out yet.  One of my "clear the list" routines is ganking things that don't deserve to be removed too soon, but so far I think it's working as (mostly) intended.

It at least highlights the concept of how it should be working, once I get this last little gremlin out of it, at this point.  Feel free to give it a go and test my little exercise in associating things together for yourself.  ;D
* Learning Program.txt (Filesize: 0.23 KB, Downloads: 153)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Marked as best answer by SMcNeill on November 10, 2020, 07:20:36 am

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Association
« Reply #7 on: November 10, 2020, 12:19:35 pm »
I think I've got the glitch sorted out now, and my little association game should  be working as intended at this point.

Code: QB64: [Select]
  1. REDIM SHARED Nam(0) AS STRING, Des(0) AS STRING 'Names and Descriptions
  2. REDIM SHARED PA(0) AS LONG, PQ(0) AS LONG 'Possible Answers and Possible Questions
  3. DIM SHARED NoN AS LONG, NoD AS LONG 'Number of Names and Descriptions
  4. DIM SHARED LoS AS LONG 'Length of String
  5.  
  6.  
  7. OPEN "Learning Program.txt" FOR BINARY AS #1
  8. IF LOF(1) = 0 THEN 'it's a blank file
  9.     PUT #1, 1, NoN 'put 0's in the file
  10.     PUT #1, 5, NoD
  11.     GET #1, 1, NoN 'else get the number of names and descriptions
  12.     GET #1, 5, NoD
  13.  
  14.  
  15. REDIM SHARED Nam(NoN) AS STRING, Des(NoD) AS STRING
  16. FOR i = 1 TO NoN
  17.     GET #1, , LoS
  18.     t$ = SPACE$(LoS)
  19.     GET #1, , t$
  20.     Nam(i) = t$ 'This should just be a name of something, like Steve, or Steve's car
  21. FOR i = 1 TO NoD
  22.     GET #1, , LoS
  23.     t$ = SPACE$(LoS)
  24.     GET #1, , t$
  25.     Des(i) = t$ 'This should be a descrition and numbers that it's associated with, such as "Fat" + CHR$(0) + " 1  3  5 ".  This tells us that NAMES 1, 3, and 5 are all fat.
  26. CLOSE 'And we're finished with disk access, for now.
  27.  
  28. SCREEN _NEWIMAGE(800, 600, 32)
  29.  
  30.     CLS , SkyBlue
  31.     PrintTotals
  32.     IF NoN = 0 THEN
  33.         GetNewThing
  34.     ELSE
  35.         LetMeGuess
  36.     END IF
  37.     IF _EXIT THEN ShutDown 'if the user clicks the little red X in the corner of the program window, save our data files before ending
  38.     _LIMIT 30
  39.  
  40.  
  41.  
  42. SUB PrintTotals
  43.     COLOR White: PRINT "I know";
  44.     COLOR Yellow: PRINT NoN;
  45.     COLOR White: PRINT "names, and ";
  46.     COLOR Yellow: PRINT NoD;
  47.     COLOR White: PRINT "descriptions."
  48.  
  49.  
  50. SUB LetMeGuess
  51.  
  52.     PRINT
  53.     PRINT "Since I know stuff, you think of something, and I'll try and guess at what you're thinking of!"
  54.     REDIM PA(NoN) AS LONG 'possible answers
  55.     FOR i = 1 TO NoN: PA(i) = i: NEXT 'corresponding to all options starting out
  56.     REDIM PQ(NoD) AS LONG 'possible questions/descriptors
  57.     FOR i = 1 TO NoD: PQ(i) = i: NEXT 'corresponding to all possible descriptors starting out
  58.     DO
  59.         IF UBOUND(PA) = 1 THEN 'there's only one possible answer
  60.             finished = -1
  61.             PRINT "Is what you're thinking of "; Nam(PA(1)); "?";
  62.             DO: i$ = UCASE$(INPUT$(1)): LOOP UNTIL i$ = "Y" OR i$ = "N"
  63.             IF i$ = "Y" THEN
  64.                 Gloat
  65.                 finished = -1
  66.             ELSE
  67.                 GetNewThing 'if we didn't guess the right thing, then the user needs to give us a new thing!
  68.             END IF
  69.         ELSEIF UBOUND(PA) = 0 THEN
  70.             GetNewThing 'no possible answers left, so no matter how many questions we ask, we can't give an answer to the game.
  71.             'May as well give up at this point and ask for the new thing.
  72.             finished = -1
  73.         ELSE
  74.             IF UBOUND(PQ) > 0 THEN 'As long as we have questions to guess, we can keep playing the game.
  75.                 guess = INT(RND * UBOUND(PQ)) + 1
  76.                 t$ = Des(PQ(guess)) 'our guess data line
  77.                 l = INSTR(t$, CHR$(0)) 'with the breakpoint between descriptor and entry values at point l
  78.                 d$ = LEFT$(t$, l - 1) 'the descriptor itself
  79.                 t$ = MID$(t$, l + 1) 'all the records which reference this descriptor
  80.                 PRINT "Is what you're thinking of "; d$; "?";
  81.                 DO: i$ = UCASE$(INPUT$(1)): LOOP UNTIL i$ = "Y" OR i$ = "N"
  82.  
  83.                 PQ(guess) = 0 'remove this question from our line up of future possible questions.
  84.                 IF i$ = "Y" THEN 'it is one of these type items.  Eliminate anything without this particular tag.
  85.                     FOR i = 1 TO UBOUND(PQ)
  86.                         s$ = " " + STR$(PQ(i)) + " "
  87.                         IF INSTR(t$, s$) = 0 THEN PQ(i) = 0 'remove all questions which don't reference this tag.
  88.                     NEXT
  89.                     FOR i = 1 TO UBOUND(PA)
  90.                         s$ = " " + STR$(PA(i)) + " "
  91.                         IF INSTR(t$, s$) = 0 THEN PQ(i) = 0 'remove all answers which don't reference this tag.
  92.                     NEXT
  93.                 ELSE 'it's not one of these type items.  Eliminate everything that holds this tag.
  94.                     FOR i = 1 TO UBOUND(PA)
  95.                         s$ = " " + STR$(PA(i)) + " "
  96.                         IF INSTR(t$, s$) <> 0 THEN PA(i) = 0 'remove all answers which reference this tag.
  97.                     NEXT
  98.                 END IF
  99.  
  100.                 UpdateOptions
  101.                 'Look at our remaining answers and eliminate any questions which don't relate to one of them
  102.                 FOR i = 1 TO UBOUND(PQ)
  103.                     IF PQ(i) <> 0 THEN
  104.                         t$ = Des(PQ(i)) 'our guess data line
  105.                         l = INSTR(t$, CHR$(0)) 'with the breakpoint between descriptor and entry values at point l
  106.                         q$ = " " + MID$(t$, l + 1) + " " 'the question/descriptor itself
  107.                         FOR j = 1 TO UBOUND(PA)
  108.                             IF PA(j) = 0 THEN EXIT FOR
  109.                             s$ = " " + STR$(PA(j)) + " " 'the search string
  110.                             IF INSTR(q$, s$) <> 0 THEN EXIT FOR
  111.  
  112.                         NEXT
  113.                         IF j > UBOUND(PA) THEN PQ(i) = 0 'if there's no answers left which matches this question, toss it out
  114.                     END IF
  115.                 NEXT
  116.                 UpdateOptions
  117.             ELSE
  118.                 'At this point, we've ran out of questions.
  119.                 IF UBOUND(pa) > 0 THEN 'If there's any answers left, let's just take a shot in the dark that it might be one of them.
  120.                     guess = INT(RND * UBOUND(pa)) + 1
  121.                     PRINT "Is what you're thinking of "; Nam(PA(guess)); "?";
  122.                     DO: i$ = UCASE$(INPUT$(1)): LOOP UNTIL i$ = "Y" OR i$ = "N"
  123.                     IF i$ = "Y" THEN Gloat ELSE GetNewThing 'if we didn't guess the right thing, then the user needs to give us a new thing!
  124.                 ELSE
  125.                     GetNewThing 'We don't have any possible questions left, nor any possible answers to give.
  126.                     'We're completely stumped at this point, and we need to get this marvelous new thing so we can learn more about it!
  127.                 END IF
  128.                 finished = -1
  129.             END IF
  130.         END IF
  131.         CLS , SkyBlue
  132.     LOOP UNTIL finished
  133.  
  134.  
  135. SUB ShutDown
  136.     OPEN "Learning Program.txt" FOR OUTPUT AS #1: CLOSE #1 'kill the old data file
  137.     OPEN "Learning Program.txt" FOR BINARY AS #1
  138.  
  139.     PUT #1, 1, NoN 'put number of names in file
  140.     PUT #1, 5, NoD 'put number of descriptions in file
  141.  
  142.     FOR i = 1 TO NoN
  143.         LoS = LEN(Nam(i))
  144.         t$ = SPACE$(LoS)
  145.         PUT #1, , LoS 'put the length of the name to file
  146.         PUT #1, , Nam(i) 'put the name to file
  147.     NEXT
  148.     FOR i = 1 TO NoD
  149.         LoS = LEN(Des(i))
  150.         t$ = SPACE$(LoS)
  151.         PUT #1, , LoS 'put the length of the name to file
  152.         PUT #1, , Des(i) 'put the description to file
  153.     NEXT
  154.     CLOSE 'And we're finished with disk access, for now.Stev
  155.     SYSTEM 'And we're finished with the program as well!
  156.  
  157.  
  158. SUB GetNewThing
  159.     PRINT
  160.     INPUT "What do you call this marvelous new thing that I know nothing about =>"; n$
  161.     n$ = _TRIM$(n$)
  162.     IF n$ = "" THEN ShutDown
  163.     FOR i = 1 TO NoN
  164.         IF _STRICMP(Nam(i), n$) = 0 THEN EXIT FOR
  165.     NEXT
  166.     IF i <= NoN THEN 'we already have such a thing
  167.         PRINT
  168.         PRINT "You've already told me about "; n$; ".  ";
  169.         t$ = " " + STR$(i) + " "
  170.         FOR j = 1 TO NoD
  171.             IF INSTR(Des(j), t$) THEN OUT$ = OUT$ + LEFT$(Des(j), INSTR(Des(j), CHR$(0)) - 1) + ","
  172.         NEXT
  173.         OUT$ = "So far, I know it's " + LEFT$(OUT$, LEN(OUT$) - 1) + "."
  174.     ELSE 'otherwise, it's something new to learn about
  175.         OUT$ = "So far, I don't know anything about this " + n$ + "."
  176.         NoN = NoN + 1
  177.         REDIM _PRESERVE Nam(NoN) AS STRING
  178.         Nam(NoN) = n$
  179.     END IF
  180.     index = i 'this is the index reference of the name we're working with
  181.     PRINT OUT$
  182.     PRINT
  183.     PRINT "Tell me three new things which describe this "; n$
  184.     FOR i = 1 TO 3
  185.         LearnNewThing:
  186.         PRINT "Thing #" + _TRIM$(STR$(i)) + "=>";
  187.         INPUT " "; t$ 'learn something about this thing
  188.         t$ = _TRIM$(t$)
  189.         'check to see if the thing we're learning is already a listed trait
  190.         FOR j = 1 TO NoD
  191.             t1$ = LEFT$(Des(j), INSTR(Des(j), CHR$(0)) - 1)
  192.             IF _STRICMP(t$, t1$) = 0 THEN EXIT FOR 'yep, it already exists.  No need to look further
  193.         NEXT
  194.         IF j <= NoD THEN 'we found an existing item
  195.             t1$ = " " + STR$(index) + " "
  196.             IF INSTR(Des(j), t1$) THEN 'we already have this fact associated with this descriptor
  197.                 COLOR Red
  198.                 PRINT "Sorry.  I already knew this about "; n$; ".  Tell me some new thing about it, instead."
  199.                 COLOR White
  200.                 GOTO LearnNewThing
  201.             ELSE
  202.                 Des(j) = Des(j) + " " + STR$(index) + " " 'add this fact to this descriptor
  203.             END IF
  204.         ELSE 'we're learning a completely new descriptor
  205.             NoD = NoD + 1
  206.             REDIM _PRESERVE Des(NoD) AS STRING
  207.             Des(NoD) = t$ + CHR$(0) + " " + STR$(index) + " "
  208.         END IF
  209.     NEXT
  210.  
  211. SUB Gloat
  212.     BEEP
  213.     BEEP
  214.     PRINT
  215.     PRINT
  216.     PRINT "I WON!  I'M SMARTER THAN YOU ARE!  I'VE LEARNED A LOT!!"
  217.     BEEP
  218.     BEEP
  219.     _DELAY 3
  220.  
  221. SUB UpdateOptions
  222.     FOR i = 1 TO UBOUND(PA)
  223.         IF PA(i) <> 0 THEN pa = pa + 1: PA(pa) = PA(i)
  224.     NEXT
  225.     FOR i = 1 TO UBOUND(PQ)
  226.         IF PQ(i) <> 0 THEN pq = pq + 1: PQ(pq) = PQ(i)
  227.     NEXT
  228.     REDIM _PRESERVE PA(pa) AS LONG
  229.     REDIM _PRESERVE PQ(pq) AS LONG
  230.  

One thing I love about coding is being to write lines such as this one:

Code: QB64: [Select]
  1.                   IF i$ = "Y" THEN Gloat ELSE GetNewThing

Feel free to start the game from scratch (it'll create a text file in the folder where you place the EXE), or use the cheesy little test file from above with it. 

To quit, click the little red "X" in the top right corner of the window (it saves automatically before shutting down), or just give it a blank entry when it asks you for the next great thing which it doesn't know.

The more you run it, the more associations it forms, and the harder it becomes to beat (unless you cheat it of course, by telling it something like 'Steve is a man', and then lying to it when it asks, 'Are you thinking of a man?'...)

Less than 250 lines for a "self-learning" program.  That's not too bad, I don't think.  ;D
« Last Edit: November 10, 2020, 12:20:46 pm by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Kiara87

  • Forum Regular
  • Posts: 164
    • View Profile
Re: Association Game
« Reply #8 on: November 10, 2020, 12:26:20 pm »
I too would like to compete but first I have to learn and I can't but in my opinion the reason is because I don't understand your language I don't understand English and this is difficult for me to understand all your advice which are excellent advice from excellent programmers like everyone else you thanks steven for taking this initiative if one day I will also be able to make my version I will post my results here
se avessi solo un'ora per salvare il mondo, passerei 55 minuti per definire bene il problema e 5 a trovare la soluzione

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Association Game
« Reply #9 on: November 10, 2020, 10:55:09 pm »
Hey Steve that program sure wasn't easy to quit when you get tired of describing things of the world.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Association Game
« Reply #10 on: November 10, 2020, 11:22:21 pm »
Hey Steve that program sure wasn't easy to quit when you get tired of describing things of the world.

The Red "X" in the top right corner should quit, and you should be able to enter a blank thing for it to quit when it asks for something new.

Though honestly, there does need to be an ONTIMER event to check the _EXIT condition a bit more than just once each main loop...   :D
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Association Game
« Reply #11 on: November 10, 2020, 11:24:33 pm »
Oh man I didn't even see the red x, but I do remember you mentioning it now that you've mentioned it again :)


Here is 63 fun packed LOC, gonna be hard to beat!
Code: QB64: [Select]
  1. _TITLE "Guess the Forum Member" 'watching LOC, see code notes at bottom
  2. REDIM A$(200) ' can you use up 200 lines?
  3. L$ = CHR$(10) ' L is for line or next line
  4. PRINT L$ + " Think of a forum member and this program will try and guess 'em or 'er." + L$
  5. IF _FILEEXISTS("Forum Member.DAT") THEN
  6.     OPEN "Forum Member.DAT" FOR INPUT AS #3
  7.     FOR I = 0 TO 200
  8.         INPUT #3, A$(I)
  9.     NEXT
  10.     CLOSE #3
  11.     FOR I = 0 TO 3
  12.         READ A$(I)
  13.     NEXT I
  14. N = VAL(A$(0))
  15.     DO
  16.         A$ = LCASE$(npt$(L$ + " Are you thinking of a forum member (y/n/list)"))
  17.         IF A$ = "list" THEN
  18.             PRINT L$ + " Members I already know are:" ' listing computers aimals
  19.             FOR I = 1 TO 200
  20.                 IF LEFT$(A$(I), 2) = "\A" THEN PRINT "   " + MID$(A$(I), 3),
  21.             NEXT I
  22.             PRINT L$ + L$
  23.         END IF
  24.     LOOP UNTIL LEFT$(A$, 1) = "y" OR LEFT$(A$, 1) = "n"
  25.     IF LEFT$(A$, 1) = "n" THEN EXIT DO 'bug out main DO doesn't want to play
  26.     K = 1
  27.     WHILE LEFT$(A$(K), 2) = "\Q"
  28.         Q$ = A$(K)
  29.         DO 'parse question and use as prompt to get c$
  30.             C$ = UCASE$(LEFT$(npt$(" " + MID$(Q$, 3, INSTR(3, Q$, "\") - 3)), 1))
  31.         LOOP UNTIL C$ = "Y" OR C$ = "N"
  32.         X = INSTR(3, LEFT$(Q$, LEN(Q$) - 1), "\" + C$)
  33.         Y = INSTR(X + 1, Q$, "\")
  34.         K = VAL(MID$(Q$, X + 2, Y - X - 2))
  35.         IF LEN(A$(K)) = 0 THEN EXIT DO ' bug out of main DO loop, out of questions
  36.     WEND
  37.     A$ = UCASE$(npt$(" Is it " + RIGHT$(A$(K), LEN(A$(K)) - 2)))
  38.     IF LEFT$(A$, 1) = "Y" THEN PRINT " Why not try another member?": _CONTINUE 'skip rest of this loop
  39.     V$ = npt$(" The member you were thinking of is ")
  40.     X$ = npt$(" Please type a question that would distinguish " + V$ + " from " + RIGHT$(A$(K), LEN(A$(K)) - 2))
  41.     DO
  42.         A$ = UCASE$(LEFT$(npt$(" For " + V$ + " the answer would be "), 1))
  43.     LOOP UNTIL A$ = "Y" OR A$ = "N"
  44.     IF A$ = "Y" THEN B$ = "N" ELSE B$ = "Y"
  45.     Z1 = VAL(A$(0))
  46.     A$(0) = _TRIM$(STR$(Z1 + 2))
  47.     A$(Z1) = A$(K)
  48.     A$(Z1 + 1) = "\A" + V$
  49.     A$(K) = "\Q" + X$ + "\" + A$ + _TRIM$(STR$(Z1 + 1)) + "\" + B$ + _TRIM$(STR$(Z1)) + "\"
  50. OPEN "Forum Member.DAT" FOR OUTPUT AS #2
  51. FOR I = 0 TO 200
  52.     WRITE #2, A$(I)
  53. DATA "4","\QWas he at [abandoned, outdated and now likely malicious qb64 dot net website - don’t go there]\Y2\N3\","\ASteve","\ASierraKen"
  54. FUNCTION npt$ (prompt$) ' nice way to get more variable data into the prompt!
  55.     PRINT prompt$;
  56.     INPUT npt$
  57.  
  58. '===================================== Code Notes down here to watch LOC of this app =======================================
  59.  
  60. ' 2020-11-10 b+ Makeover = 2nd go at Midnight Owl's great start on conversion
  61. ' note: first mod of Midnight's converstion added UCASE$ to all INPUTs solving a little user friendly problem.
  62. ' 2020-11-10 A step further was to revise PRINT prompt : INPUT A$ : UCASE$(A$) into a single function.
  63. ' Then one time call sub routines I put back into main code includeing the MAIN SUB!
  64. ' That includes: the LIST animals SUB for LIST option (which I made known in prompt).
  65. ' The SHOW QUESTION SUB and the KEEPDATA SUB.
  66. ' I also removed the ON error and used more modern check _FILEEXISTS
  67.  
  68. ' savings: admittedly some of this makes understanding of code less clear
  69. ' for X, Y use INSTR to skip FOR... NEXT loops
  70.  
  71. ' From this:
  72. ' T$ = "\" + C$
  73. ' FOR X = 3 TO LEN(Q$) - 1
  74. '     IF MID$(Q$, X, 2) = T$ THEN EXIT FOR
  75. ' NEXT X
  76.  
  77. ' to this
  78. ' X = INSTR(3, LEFT$(Q$, LEN(Q$) - 1), "\" + C$)
  79.  
  80. ' and from this
  81. ' FOR Y = X + 1 TO LEN(Q$)
  82. '     IF MID$(Q$, Y, 1) = "\" THEN EXIT FOR
  83. ' NEXT Y
  84. ' to this!
  85. ' Y = INSTR(X + 1, Q$, "\")
  86.  
  87. ' I decided to get rid of all the CAPITALIZATION and make normal looking sentences. So I modified the INPUT Function
  88. ' renaming it npt$ (input$ without vowels). Now if you want all caps use: UCASE$(npt$(prompt$)), same for lower
  89. ' case. Since humans swim I changed the first question: Does it live in water? Now how would you answer that for beaver?
  90. ' I hope not yes :) Nor do beavers or rabbits hibernate, I am learning myself!
  91. ' 2020-11-10 post 64 LOC down from original 73, not bad since it is complete makeover.
  92.  
  93. ' 2020-11-10 repurpose to Guess the Forum Member
  94.  
  95.  
  96.  

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Association Game
« Reply #12 on: November 10, 2020, 11:38:56 pm »
The normal red X that closes a window does not work when getting quizzed, must be INPUT blocking.

OK so I enter nothing and it quits :)

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Association Game
« Reply #13 on: November 10, 2020, 11:44:32 pm »
The normal red X that closes a window does not work when getting quizzed, must be INPUT blocking.

OK so I enter nothing and it quits :)

I've got the red "X" disabled, until it gets to line 45 and processes it, so you can click it, and then it doesn't do anything until the proper spot in the process order.   Really, not the way one wants to go when trapping an _EXIT, but INPUT is a PITA and doesn't play nice with... Well, with anything really.  :P

What I need to do is swap input for a custom routine (I've got one of those somewhere), and then use an ONTIMER event to check for the exit click every little bit.  It'd make quitting much easier to accomplish without someone ending up resorting to task manager to get the job done.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!