Link to home
Start Free TrialLog in
Avatar of giiggsy
giiggsy

asked on

computer interaction in the game of Nim!

I am creating a game called nim using VB. This game involves having 4 rows of counters. It is a two player game with one player being the computer. The two players take turns taking counters away and the person taking the last counter wins. A player can take any number of counters away on their go but only from one row out of the four at a time. So on one go a player CAN take all the counters from row 3 if they want to, but from no other row until their next go.

The rows are as follows:

row1 = 1 counter
row2 = 3 counters
row3 = 5 counters
row4 = 7 counters

I used labels to represent the counters. There are fifteen counters in total and the names of the counters start from; cmdCounter and end at cmdCounter15.

I put the following code in the click event of each counter, adjusting the counter name accordingly:

Private Sub cmdCounter_Click()
cmdCounter.Visible = False

End Sub

Having done that for each counter I can now take the counter away when I click on it, which is good. Heres wher i get to my problem.

As i said previously the game is two player with one player being the computer. I want to add a command button so that when the user has taken their turn they click on this command button, so that the computer takes its turn and removes counters. I am not very good at writing code and i was wondering if anyone out their can help me with this problem. I really have no idea where to begin in even thinking of this sort of code!!

Anyone got any ideas??


Avatar of Otana
Otana

If you don't want your computer to play smart, you can just use a random.

i=int(rnd*50)+1 will get you a random chosen number ranging from 1-50

Avatar of giiggsy

ASKER

Otana,

I put that in the click event of the command button and nothing happend.Am i meant to put that in the click event?Im sorry if i sound thick but im being serious when I say that VB skills are not that good.
after you make a counter invisible, it's the computers turn. So, after making a counter invisible, make loop that generates a random number which could be any number between 1 and the number of counters you have. Keep looping until you have a number of a counter that's still visible. If you have one, make that counter invisible.

Are you using a control array for your cmdCounters?

If you want I can post a bit of code, but I need to know whether you have a control array or separate counters.

Sorry, I just noticed the info is already in your first post.

Do you know how to create a control array? It would be much easier, since you have to put code in only one event.
Avatar of giiggsy

ASKER

Hi Otana,

No I havent used a control array I've set up sixteen seperate counters.
This is how you make a control array:

Place a label on your form, name it cmdCounter. Right-click on it, select copy. Now right-click on the form and select paste. You will asked if you want to create a control array, say "yes". The new label will appear in the upper left corner of your form. It will be the same as the first, but it will have an index of 1, whereas your first had an index of 0. Place the new label where you want it to.

Now paste all the other labels as well, you don't have to copy anymore.

Your on_click event should become:

Private Sub cmdCounter_Click(index as integer)
dim i as integer

cmdCounter(index).Visible = False

Do until cmdCounter(i).visible=true
  i=int(rnd*15)+1
Loop

cmdCounter(i).visible=false

End Sub



That should do the trick.

make this:

i=int(rnd*15)+1

into:

i=int(rnd*16)

Sorry, I read your question a bit too hasty, I thought you had only 15 counters.
The +1 is not necessary either, because your index goes from 0 to 15.

Hope I could be of help...
Avatar of giiggsy

ASKER

Hi Otana

what you did worked but i think i need the computer to be smarter.

With the code you gave me, as soon as i click on a counter another dissapears indicating the computer has taken its turn. But remeber when i said earlier that when its the users go (or the computers go) they can take away as many counters they like in one go as long as their on the same row, the user cannot do that with the code you gave me.

Thats why i need a command button to ensure the computer has its go. So if a user decides to take all the counters away from row four, the user can then press the comman dbutton so the computer will make its move in reply.

I hope i havent confused you too much.
Ok, no problem, just move the following code to the onclick event of a command button:

Private Sub cmdComputerTurn_Click()
  dim i as integer

  Do until cmdCounter(i).visible=true
    i=int(rnd*15)+1
  Loop

  cmdCounter(i).visible=false
End Sub

This way you can take as much counters as you like before letting the computer play.

For a bit more intelligence on the computers' part, I would say you need to use two randoms:

1 to pick a row that has still visible counters, and 1 to pick any number of counters on that row.

So I would use 4 variables to store the number of counters left on a row.

dim Row(1 to 4) as integer

At the beginning of the game, set:
Row(1)=1
Row(2)=3
Row(3)=5
Row(4)=7

Every time a player clicks a counter, subtract 1 from that row.

So, on the computers' turn, do the following:

Do until row(i)>0
  i=int(rnd*4)+1
Loop

j=int(rnd*row(i))+1

So j gives the number of counters the computer will take away.

Avatar of giiggsy

ASKER

Otana,

I applied the code to the command button and it now lets me take at urn and give the computer a turn.

However, i didnt quite understand the following part of your answer, when you wrote

"So I would use 4 variables to store the number of counters left on a row.

dim Row(1 to 4) as integer

At the beginning of the game, set:
Row(1)=1
Row(2)=3
Row(3)=5
Row(4)=7

Every time a player clicks a counter, subtract 1 from that row.

So, on the computers' turn, do the following:

Do until row(i)>0
 i=int(rnd*4)+1
Loop

j=int(rnd*row(i))+1"

Where do i set the variables at the beginning of the game? And wher would i put the code that you gave me as the last part of your answer?

Thanks for your help it really is appreciated.


 
make Row(1 to 4) a global variable, declare it at form level. (before you declare any subs or functions)

I suppose you have a button that resets all counter to visible, to start a new game?

That's where this code should be:

Private sub cmdNewGame_Click()
  Row(1)=1
  Row(2)=3
  Row(3)=5
  Row(4)=7
End sub

These variables indicate the number of counters left on a row, so it is the maximum numbers of counters the computer may choose to remove. Therefor, you should put this also in your counter click event:

private sub cmdCounter_click(index as integer)
dim ActiveRow as integer
  select case index
    case 0
      ActiveRow=1
    case 1,2,3
      ActiveRow=2
    case 4,5,6,7,8
      ActiveRow=3
    case else
      ActiveRow=4
  end select

  'Here comes the code I already gave you

  Row(ActiveRow)=Row(ActiveRow)-1
end sub  

So, at the end of the event, you tell your program there's one less counter on that row.

This does require you to place your indexes on the form in this order:

0
1  2  3
4  5  6  7  8
9  10 11 12 13 14 15

Because otherwise, the program will not know from what line you took a counter.

I hope this is still clear. : )

The following piece of code is to make the program pick a row that has counters on it:

Do until row(i)>0
 i=int(rnd*4)+1
Loop

Then, you need to make the program pick a random number of coutners to take away, but it may not exceed the number of counters still visible.

j=int(rnd*row(i))+1

So j gives the number of counters the computer will take away.

Those last two pieces of code will come in the Click event of the cmdComputerTurn.

It's also important to decide which counters will be taken then, so I need to know this:

Can you take any counter(s) away or will you always have to take the last in a row first?


Avatar of giiggsy

ASKER

You can take ANY amount of counters from one row on one go. You are not allowed to take counters from one row and then take away counters from another row in the same go.

Thanks for your help Otana. I'm going to have to go now but I will be back in tomorrow and check any response you may have given.

Thanks for all the help you've given me! it really is appreciated.

Thanks
No problem. If you have an e-mail adress, I can send some more code to you, but I understand if you do not want to put your address here in public.
Still one more question!

I understand you can take only counters from the same row, and that it can be any number of counters.

What I mean is, can it be the first in the row if there are still others in that row?

This is important to decide the way in which the program has to decide which counters to take away on the computers' turn.

example:

0
1  2  3
4  5  6  7  8
9  10 11 12 13 14 15

if you have this on the board, and you take two counters from row 2, can it become:

0
      3
4  5  6  7  8
9  10 11 12 13 14 15

or should it become:

0
1
4  5  6  7  8
9  10 11 12 13 14 15



Avatar of giiggsy

ASKER

Hi Otana,

Im back again.

In the last response you gave me before i left on Friday, you said:

"make Row(1 to 4) a global variable, declare it at form level. (before you declare any subs or functions)"

How do I make a global variable? I cant even do the simplest things!

If you look at the code of your program, you have a series of subs, functions, events, ... programmed. Above all code, you can declare variables, these will then be known throughout your form, in all events, subs, functions.

So declaring a global variable is like declaring any other variable, only you do it outside your subs, functions, events,...

Type following line at the top of your code page:

dim Row(1 to 4) as integer
Avatar of giiggsy

ASKER

I put that at the top of my code page and I made all the other changes to the code like you suggested, but now when i run the game and select the "computers turn" command button the following error comes up:

"run Time Error 9
subscript out of range"

I selected "De-Bug" and it highlights the following piece of code in "cmdCompTurn"

Do Until Row(i) > 0
i = Int(Rnd * 4) + 1
Loop

Any Ideas?
Normally that shouldn't happen, but you can build a safety in your loop:

Do Until Row(i) > 0
  i = Int(Rnd * 4) + 1
  Select case i
    case 0
      i=1
    case 5
      i=4
  End Select
Loop

Subscript out of range happens when the index is a number outside your defined range. In this case, your range goes from 1 to 4, so 0 or 5 would give the error. Normally, with the Rnd function you would not get those results, but it's always possible.

Another remark, in your form_load event, you might want to put following command:

randomize

Otherwise, you may notice that the computer will often make identical moves.
Avatar of giiggsy

ASKER

Otana I did what you said and the same thing happend. It said subscript out of range.

Here is the full code i have put there:

Private Sub cmdCompTurn_Click()
Dim i As Integer

 Do Until cmdCounter(i).Visible = True
   i = Int(Rnd * 15) + 1
 Loop
Do Until Row(i) > 0
 i = Int(Rnd * 4) + 1
 Select Case i
   Case 0
     i = 1
   Case 5
     i = 4
 End Select
Loop


 cmdCounter(i).Visible = False
 j = Int(Rnd * Row(i)) + 1
End Sub

When i select DEBUG it highlights:"Do Until Row(i) > 0"

Have i written something wrong in the code?
Sorry, my mistake. Place the "Until ..." parts, that are now behind Do, after Loop.

So, you should get (for both loops):

Do
  ...
Until ...
Also, this line:

cmdCounter(i).Visible = False

should be between the two loops, not after the second.
Avatar of giiggsy

ASKER

I made the changes. However, now I can remove a counter but when i select "computers turn" a counter goes white then the game freezes and nothing responds so i have to stop it through the task manager. In the task manager it says its not responding.  

Have i written the right code for the "computers turn"?

Private Sub cmdCompTurn_Click()
Dim i As Integer

 Do
   i = Int(Rnd * 15) + 1
 Loop Until cmdCounter(i).Visible = True

 cmdCounter(i).Visible = False
 Do
i = Int(Rnd * 4) + 1
Loop Until Row(i) > 0
j = Int(Rnd * Row(i)) + 1

End Sub

make sure you have this code also, otherwise your Row() values won't be set, and your loops will continue eternally.

Private sub cmdNewGame_Click()
 Row(1)=1
 Row(2)=3
 Row(3)=5
 Row(4)=7
End sub



I'm writing the code for computerturn, give me a few minutes to finish it, and then I can give the entire code at once, because now it's getting too difficult with all those little pieces of code.
Ok, this should be ot. Remove all code I already gave you from the cmdCompTurn_Click event, then place all following code in its place.

Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim l As Integer

'/// Decide which row to take stones from
Do Until Row(i) > 0
    i = Int(Rnd * 4) + 1
Loop Until Row(i) > 0

'/// Decide how many stones to take
j = Int(Rnd * Row(i)) + 1

'/// Decide which stones to take
Select Case i
    Case 1
        cmdCounter.Visible = False
        Row(1) = 0
    Case 2
        For k = 1 To j
            Do
                l = Int(Rnd * 3) + 1
            Loop Until cmdCounter(l).Visible = True
            cmdCounter.Visible = False
            Row(2) = Row(2) - 1
        Next k
    Case 3
        For k = 1 To j
            Do
                l = Int(Rnd * 5) + 4
            Loop Until cmdCounter(l).Visible = True
            cmdCounter.Visible = False
            Row(3) = Row(3) - 1
        Next k
    Case 4
        For k = 1 To j
            Do
                l = Int(Rnd * 5) + 9
            Loop Until cmdCounter(l).Visible = True
            cmdCounter.Visible = False
            Row(4) = Row(4) - 1
        Next k
End Select





I'll explain a bit how it works:

First, you want to pick a row where the ocmputer is going to take stones from. So, you loop until you find a row that still has stones.

Second, you want to know how much stones the computer will take. So you create a random that picks a number between 1 and the number of stones left on that row.

Three, you decide which stones the computer will take. So you create a random that loops through the stones on the chosen row, until you find a stone that's visible. Make it invisible, and subtract 1 from Row(i), to indicate that there is one less stone on that row.

Repeat step three for the number of stones you have to remove.



I know this is a lot of code, so if there's anything you don't understand, just ask, I'll be happy to give some more explanation.
Avatar of giiggsy

ASKER

I did the changes like you said but now when i select Computers turn this error message comes up:

"method or data memeber not found"

I select ok and it highlights the following line of code:
"cmdCounter.Visible = False"

"visible" becomes highlighted.
ASKER CERTIFIED SOLUTION
Avatar of Otana
Otana

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of giiggsy

ASKER

Sorry about this Otana.

I did what you said again, and this time when I select computerturn, it freezes on me again. Ive put the following code in "cmdNewTurn" so i dont think it would have anything to do with the loops.

Private sub cmdNewGame_Click()
Row(1)=1
Row(2)=3
Row(3)=5
Row(4)=7
End sub


Would it be easier if i emailed you what ive done so far, because i might be missing something out. If you dont want to put your email address on here you can email me on sukhdeep.rai1@orange.net If you send me an email i'll reply to it and send you what ive done so far. Is that Ok?
ok, I've sent you an e-mail.
Avatar of giiggsy

ASKER

After help through email Otana resolved my question.

The final code looked like this:

Dim Row(1 To 4) As Integer
Private Sub cmdCompTurn_Click()
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim l As Integer


Do
   i = Int(Rnd * 4) + 1
Loop Until Row(i) > 0

j = Int(Rnd * Row(i)) + 1

Select Case i
  Case 1
      cmdCounter(0).Visible = False
      Row(1) = 0
  Case 2
      For k = 1 To j
          Do
              l = Int(Rnd * 3) + 1
          Loop Until cmdCounter(l).Visible = True
          cmdCounter(l).Visible = False
          Row(2) = Row(2) - 1
      Next k
  Case 3
      For k = 1 To j
          Do
              l = Int(Rnd * 5) + 4
          Loop Until cmdCounter(l).Visible = True
          cmdCounter(l).Visible = False
          Row(3) = Row(3) - 1
      Next k
  Case 4
      For k = 1 To j
          Do
              l = Int(Rnd * 7) + 9
          Loop Until cmdCounter(l).Visible = True
          cmdCounter(l).Visible = False
          Row(4) = Row(4) - 1
      Next k
End Select







End Sub

Private Sub cmdCounter_Click(Index As Integer)
Dim ActiveRow As Integer
 Select Case Index
   Case 0
     ActiveRow = 1
   Case 1, 2, 3
     ActiveRow = 2
   Case 4, 5, 6, 7, 8
     ActiveRow = 3
   Case Else
     ActiveRow = 4
 End Select

cmdCounter(Index).Visible = False
Row(ActiveRow) = Row(ActiveRow) - 1

End Sub

Private Sub cmdNewGame_Click()
Row(1) = 1
Row(2) = 3
Row(3) = 5
Row(4) = 7
Dim i As Integer
'cmdCounter(Index).Visible = True
'cmdCounter(3).Visible = True
'cmdCounter(1).Visible = True
'cmdCounter(2).Visible = True
'cmdCounter(4).Visible = True
'cmdCounter(5).Visible = True
'cmdCounter(6).Visible = True
'cmdCounter(7).Visible = True
'cmdCounter(8).Visible = True
'cmdCounter(9).Visible = True
'cmdCounter(10).Visible = True
'cmdCounter(11).Visible = True
'cmdCounter(12).Visible = True
'cmdCounter(13).Visible = True
'cmdCounter(14).Visible = True
'cmdCounter(15).Visible = True
For i = 0 To 15
  cmdCounter(i).Visible = True
Next i
End Sub





Private Sub Form_Load()
Row(1) = 1
Row(2) = 3
Row(3) = 5
Row(4) = 7

Randomize

End Sub


Thanks Otana