Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ MUSHclient ➜ General ➜ Script help

Script help

It is now over 60 days since the last post. This thread is closed.     Refresh page


Posted by Shady Stranger   USA  (45 posts)  Bio
Date Fri 11 Mar 2011 03:02 AM (UTC)
Message
I am working on a deck of cards script based on the Italian deck. I was planning on making the final version of it a plugin. So far, I only have a random card generator. I need to remove a card from being drawn again after it has been drawn. I am not sure if this is even possible based on the way I have scripted my card generator. Any help would be greatly appreciated.

Version 4.72 and I apologize for the length.

<aliases>
  <alias
   match="draw card"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>
local total = 0

for i = 1, 1 do

  result = math.floor (MtRand () * 40)
  
end

if result == 0 then

Send (" ")
Note ("Ace of Swords")

end

if result == 1 then

Send (" ")
Note ("Two of Swords")

end

if result == 2 then

Send (" ")
Note ("Three of Swords")

end

if result == 3 then

Send (" ")
Note ("Four of Swords")

end

if result == 4 then

Send (" ")
Note ("Five of Swords")

end

if result == 5 then

Send (" ")
Note ("Six of Swords")

end

if result == 6 then

Send (" ")
Note ("Seven of Swords")

end

if result == 7 then

Send (" ")
Note ("Page of Swords")

end

if result == 8 then

Send (" ")
Note ("Knight of Swords")

end

if result == 9 then

Send (" ")
Note ("King of Swords")

end

if result == 10 then

Send (" ")
Note ("Ace of Coins")

end

if result == 11 then

Send (" ")
Note ("Two of Coins")

end

if result == 12 then

Send (" ")
Note ("Three of Coins")

end

if result == 13 then

Send (" ")
Note ("Four of Coins")

end

if result == 14 then

Send (" ")
Note ("Five of Coins")

end

if result == 15 then

Send (" ")
Note ("Six of Coins")

end

if result == 16 then

Send (" ")
Note ("Seven of Coins")

end

if result == 17 then

Send (" ")
Note ("Page of Coins")

end

if result == 18 then

Send (" ")
Note ("Knight of Coins")

end

if result == 19 then

Send (" ")
Note ("King of Coins")

end

if result == 20 then

Send (" ")
Note ("Ace of Cups")

end

if result == 21 then

Send (" ")
Note ("Two of Cups")

end

if result == 22 then

Send (" ")
Note ("Three of Cups")

end

if result == 23 then

Send (" ")
Note ("Four of Cups")

end

if result == 24 then

Send (" ")
Note ("Five of Cups")

end

if result == 25 then

Send (" ")
Note ("Six of Cups")

end

if result == 26 then

Send (" ")
Note ("Seven of Cups")

end

if result == 27 then

Send (" ")
Note ("Page of Cups")

end

if result == 28 then

Send (" ")
Note ("Knight of Cups")

end

if result == 29 then

Send (" ")
Note ("King of Cups")

end

if result == 30 then

Send (" ")
Note ("Ace of Clubs")

end

if result == 31 then

Send (" ")
Note ("Two of Clubs")

end

if result == 32 then

Send (" ")
Note ("Three of Clubs")

end

if result == 33 then

Send (" ")
Note ("Four of Clubs")

end

if result == 34 then

Send (" ")
Note ("Five of Clubs")

end

if result == 35 then

Send (" ")
Note ("Six of Clubs")

end

if result == 36 then

Send (" ")
Note ("Seven of Clubs")

end


if result == 37 then

Send (" ")
Note ("Page of Clubs")

end

if result == 38 then

Send (" ")
Note ("Knight of Clubs")

end

if result == 39 then

Send (" ")
Note ("King of Clubs")

end</send>
  </alias>
</aliases>


Thanks!
Top

Posted by Manacle   (28 posts)  Bio
Date Reply #1 on Fri 11 Mar 2011 07:19 PM (UTC)

Amended on Fri 11 Mar 2011 07:20 PM (UTC) by Manacle

Message
You may want to start with something like this:


NUMERALS =
{"Ace", "Two", "Three", "4", "5", "6", "7", "8", "9", "10"}

SUITS = {"Swords", "Coins", "Cups", "Clubs"}

local card

<INSERT CHECK TO SEE IF DECK IS EMPTY HERE>

while not card do
rand = <YOUR CHOSEN RANDOM GENERATION FUNCTION HERE>
if not deck[rand] then card = rand end
end

deck[card] = 1

local s, n = math.floor(card/#NUMERALS)+1, card%#NUMERALS+1
Note(NUMERALS[n] .. " of " .. SUITS[s])


What I'm doing differently than you, first of all, is separating the concept of the names of the cards ("Ace of ...", "Two of ...") from the cards left in the deck (has card #1 been chosen?, has card #33 been chosen?...)

I concept a deck as an array of 40 bits. If a card is not in the deck, I set that bit as "1". If it hasn't, it remains nil.

The core logic of selecting a card is just a while loop randomly picking cards until it picks one that hasn't been chosen. This should work fine with 40 cards, although it would be a bad idea to do this if the deck were larger.

You might want to maintain a count of how many cards are chosen or otherwise run through the array very quickly before trying to pick a card to make sure there's a card left to pick, otherwise this code will enter an infinite loop.

The end of the script spits out the name of the card chosen. You can do this by first checking which suit the card number represents (divide by number of cards in a suit, 0 = spades, 1 = hearts, etc...) then which number (the "%" operator is modulus, which you might have called the "remainder" at another time in life. For example, 13 % 5 is the same as "if I divide 13 by five, I get two with a remainder of 3, so 13 % 5 = 3).

By the way, there's no reason that the code I've presented couldn't be easily modified for almost any type of "deck" of cards that follows the suit/numeral theme. Just keep the deck size small unless you improve the search algorithm.

If you put this in a plugin, I suggest you concept the function like this:
function ChooseCard(deck) returns deck, card

Every time you need a card chosen, pass the deck to this function as well and have it pass back both the card and the modified deck. That way you can use the same code to manage multiple decks at a time.
Top

Posted by Nick Gammon   Australia  (23,165 posts)  Bio   Forum Administrator
Date Reply #2 on Fri 11 Mar 2011 09:03 PM (UTC)

Amended on Fri 11 Mar 2011 09:07 PM (UTC) by Nick Gammon

Message
Another approach is to describe all the possible cards using the method described by Manacle and in a simple loop, generate all the deck.

Then using the "shuffle" algorithm presented here:

http://www.gammon.com.au/forum/?id=9908

... you shuffle the deck. Now you simply pull out a card at a time (removing it from the deck). Thus you get a random card, and eventually run out of cards.

The shuffle algorithm is in the "commas" module - you just need to "require" that to get it:



-- card names

NUMERALS = {"Ace", "Two", "Three", "4", "5", "6", "7", "8", "9", "10"}

SUITS = {"Swords", "Coins", "Cups", "Clubs"}


-- generate the deck

deck = {}

for i, suit in ipairs (SUITS) do
  for j, card in ipairs (NUMERALS) do
    table.insert (deck, card .. " of " .. suit)
  end -- for each number
end -- for each suit

-- shuffle it

require "commas"

shuffle (deck)

-- pull out a card at a time

while #deck > 0 do
  card = table.remove (deck)
  print (card)
end -- while



Example output:


8 of Swords
7 of Swords
Two of Clubs
8 of Coins
6 of Swords
7 of Clubs
5 of Cups
4 of Coins
9 of Swords
5 of Clubs
Ace of Cups
Two of Cups
10 of Cups
Three of Coins
4 of Swords
10 of Swords
8 of Cups
Ace of Swords
Two of Coins
Three of Clubs
8 of Clubs
Two of Swords
Ace of Clubs
6 of Coins
10 of Coins
6 of Clubs
9 of Clubs
5 of Swords
Ace of Coins
6 of Cups
7 of Cups
Three of Swords
10 of Clubs
7 of Coins
4 of Clubs
4 of Cups
Three of Cups
5 of Coins
9 of Coins
9 of Cups


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Shady Stranger   USA  (45 posts)  Bio
Date Reply #3 on Sat 12 Mar 2011 01:15 AM (UTC)

Amended on Sat 12 Mar 2011 01:19 AM (UTC) by Shady Stranger

Message
Thank you both for your help. After reviewing both of your responses I can firmly admit that I am in over my head on this one.

That being said...

Nick's example does just as his output states. This will work if I can also store each card as a variable so that I can call on them later.


-- pull out a card at a time

while #deck > 0 do
  card = table.remove (deck)
  print (card)
  SetVariable ("card1", card)
end -- while


Then I would need a way to delete all the variables.


<aliases>
  <alias
   match="shuffle deck"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>DeleteVariable ("card1")</send>
  </alias>
</aliases>


These work fine except that I need a way to store each card as a different variable. The above examples only target the last card and I understand why.

To further explain...Using the "Example output" from Nick's post, the 8 of swords would be stored as the variable "card1", 7 of sword is "card2", Two of Clubs is "card3" and so on all the way up to "card40".
Top

Posted by Nick Gammon   Australia  (23,165 posts)  Bio   Forum Administrator
Date Reply #4 on Sat 12 Mar 2011 01:51 AM (UTC)

Amended on Sat 12 Mar 2011 01:52 AM (UTC) by Nick Gammon

Message
Huh? You don't need to store any variables (except the deck). I thought you just wanted to draw a card at a time. So have an alias like this:


<aliases>
  <alias
   match="draw card"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>

if deck == nil or #deck == 0 then

  Note ("Shuffling new deck ...")

  -- card names
  local NUMERALS = {"Ace", "Two", "Three", "4", "5", "6", "7", "8", "9", "10"}
  local SUITS = {"Swords", "Coins", "Cups", "Clubs"}
  
  -- generate the deck
  deck = {}
  
  for i, suit in ipairs (SUITS) do
    for j, card in ipairs (NUMERALS) do
      table.insert (deck, card .. " of " .. suit)
    end -- for each number
  end -- for each suit
  
  -- shuffle it
  require "commas"
  shuffle (deck)
  
end -- if deck needs to be made

Send ("say " .. table.remove (deck))

</send>
  </alias>
</aliases>


Now, testing it:


> draw card

You say, "4 of Clubs."

> draw card

You say, "6 of Coins."


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,165 posts)  Bio   Forum Administrator
Date Reply #5 on Sat 12 Mar 2011 01:55 AM (UTC)
Message
Or to put it another way, the variable "deck" *is* the table of cards. So if you omit the bit that draws them all out, you have a table which contains all the cards.

So if you just do the first bit:


-- card names
  local NUMERALS = {"Ace", "Two", "Three", "4", "5", "6", "7", "8", "9", "10"}
  local SUITS = {"Swords", "Coins", "Cups", "Clubs"}
  
  -- generate the deck
  deck = {}
  
  for i, suit in ipairs (SUITS) do
    for j, card in ipairs (NUMERALS) do
      table.insert (deck, card .. " of " .. suit)
    end -- for each number
  end -- for each suit
  
  -- shuffle it
  require "commas"
  shuffle (deck)


Now "deck" is a variable which is a table, which has 40 entries, one for each card, like this:


1="9 of Coins"
2="Ace of Coins"
3="4 of Cups"
4="6 of Swords"
5="9 of Clubs"
6="Two of Coins"
7="5 of Clubs"
8="Two of Cups"
9="Ace of Swords"
10="Ace of Clubs"
11="5 of Cups"
12="10 of Cups"
13="9 of Swords"
14="Two of Swords"
15="7 of Cups"
16="8 of Swords"
17="7 of Clubs"
18="Ace of Cups"
19="8 of Clubs"
20="4 of Swords"
21="6 of Clubs"
22="6 of Coins"
23="Two of Clubs"
24="Three of Coins"
25="10 of Clubs"
26="8 of Cups"
27="4 of Clubs"
28="7 of Coins"
29="Three of Clubs"
30="4 of Coins"
31="10 of Swords"
32="Three of Cups"
33="7 of Swords"
34="5 of Coins"
35="Three of Swords"
36="10 of Coins"
37="6 of Cups"
38="9 of Cups"
39="8 of Coins"
40="5 of Swords"


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,165 posts)  Bio   Forum Administrator
Date Reply #6 on Sat 12 Mar 2011 01:56 AM (UTC)
Message
All that table.remove does is remove the last entry from the table, and return it. So you get one of the shuffled cards, and remove it from the deck, so you don't draw it again later.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Manacle   (28 posts)  Bio
Date Reply #7 on Sat 12 Mar 2011 02:10 AM (UTC)
Message
If you want to keep track of the pulled cards in order, you can use the table.insert method to keep two different arrays (I'd call them the shoe and the discard, but you get the idea).

table.insert(discard, table.remove(shoe)) would keep track of the cards pulled. Do you see? I'm removing a card from the face down deck (table.remove(shoe)) and immediately appending it to the end of the discard (table.insert(shoe,...)).

You could access the most recently drawn card through "discard[#discard-1]" which accesses the last element of the discard array.

Shuffling is easy. Set the discard and shoe to {} and then create a brand new shoe by the method already provided to you.

If you absolutely insist on having separate variables for each card pulled ("card1", "card2",...) don't bother deleting them. Store another variable, say "numberOfDrawnCards" and set it to zero when you first start.

Each time you draw a card, increment that counter. Then you know that any number greater than that counter (say, the variable "card11" when the counter is at 7) is garbage.

You can "delete" each variable by setting the counter to zero again.

I'd point out, though, that all you'd be doing is making a very ugly, very language inefficient array with a fixed length by doing this.
Top

Posted by Shady Stranger   USA  (45 posts)  Bio
Date Reply #8 on Sat 12 Mar 2011 03:12 AM (UTC)
Message
Quote:

Huh? You don't need to store any variables (except the deck). I thought you just wanted to draw a card at a time.


Quote:

I'd point out, though, that all you'd be doing is making a very ugly, very language inefficient array with a fixed length by doing this.


My reasoning behind storing each card as a variable is so that I can draw that card from the deck but still be able to refer to it later in a trigger. For example, let's say that I drew a card and whispered that card to Player A as their card. Since the card is stored as a variable, I can set that card to that specific player using a trigger. I can then draw another card for Player B and whisper their card to them. Player A discards their card on 1 of 4 discard piles and gets another. Player B can now pick up that discarded card or any other card that has been previously discarded if they choose to do so.

Basically, I need to store the value (card) of each item in the table so that I can use it in a trigger

This is just a part of a project (a specific card game) I am working on but I can't go any further until this part is working in a specific way. I realize that I may not be making myself clear as to what I really need the script to do. It is very likely the answer is in one of the responses and I apologize for not understanding it. I really do appreciate all of the help.
Top

Posted by Nick Gammon   Australia  (23,165 posts)  Bio   Forum Administrator
Date Reply #9 on Sat 12 Mar 2011 03:34 AM (UTC)

Amended on Sat 12 Mar 2011 03:35 AM (UTC) by Nick Gammon

Message
I still wouldn't use card1, card2 etc. when you can have the same effect much more generally as card [1], card [2] etc. Then you don't have to muck around with 40 variables.

You can still remember which player has what, like this:

Draw a card:


card = table.remove (deck)  // get a free card


Tell the player what he has:


Send ("whisper " .. playername .. " You drew: " .. card)


Save that player's card:


table.insert (player1, card)


Effectively this action moved "card" (eg. "7 of Cups") from the deck to the player's card table.

If you always balance a table.remove and a table.insert you never lose a card, you just move it somewhere else.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Manacle   (28 posts)  Bio
Date Reply #10 on Sat 12 Mar 2011 05:19 AM (UTC)

Amended on Sat 12 Mar 2011 05:46 AM (UTC) by Nick Gammon

Message
You might do better to abstract some of this away from your mind. You might just be getting hung up on the details because you're keeping too much in your head.

First, abstract your thought process to create card "zones" instead of decks and hands. A card "zone" holds list of cards that are in a specific order (note that this order can be randomized, but it is fixed).

Then try writing these functions:

function CreateNewDeck() return <a sorted deck of 40 cards>
function Shuffle(zone) -- to shuffle the cards in a zone

function DrawFirst(zone) -- remove and return the first card in this zone

function ContainsCard(zone, card) -- return true if the zone contains that card, nil otherwise

function RemoveCard(zone, card) -- remove the specific card that you pass as the second variable from the specified zone; you probably for your own convenience want to return the removed card as a result of this function

function Size(zone) -- return the number of cards in this zone

function InsertInFront(zone, card) -- put the given card into the front of this zone

function Clear(zone) -- basically, set this zone to an empty array


Once you do that, the mechanics should be easy.


local deck, discard, p1, p2, p3 = {}, {}, {}, {}, {}

deck = CreateNewDeck()
Shuffle(deck)

-- pass out cards
InsertInFront(p1, DrawFirst(deck))
InsertInFront(p2, DrawFirst(deck))
InsertInFront(p3, DrawFirst(deck))
InsertInFront(p1, DrawFirst(deck))
InsertInFront(p2, DrawFirst(deck))
InsertInFront(p3, DrawFirst(deck))
InsertInFront(p1, DrawFirst(deck))
InsertInFront(p2, DrawFirst(deck))
InsertInFront(p3, DrawFirst(deck))
InsertInFront(p1, DrawFirst(deck))
InsertInFront(p2, DrawFirst(deck))
InsertInFront(p3, DrawFirst(deck))
InsertInFront(p1, DrawFirst(deck))
InsertInFront(p2, DrawFirst(deck))
InsertInFront(p3, DrawFirst(deck))

-- allow player one to choose the cards he discards using some method, 
-- let's assume he loads the array "p1discardlist" with the cards he doesn't want
for each _, c in ipairs(p1discardlist) do
if ContainsCard(p1, c) then
InsertInFront(discard, RemoveCard(p1, c))
InsertInFront(p1, DrawFirst(deck))
end end

--repeat for each other player

--determine winner, pay money

--Let's mimick a real game for some random reason

--collect all the cards
while #p1 > 0 do
InsertInFront(discard, DrawFirst(p1))
end

--repeat for all

--load the shoe
while #discard > 0 do
InsertInFront(deck, DrawFirst(discard))
end

Shuffle(deck)


Of course, the end could easily be:

ClearZone(p1)
ClearZone(p2)
ClearZone(p3)
ClearZone(discard)

deck = Shuffle(CreateNewDeck())


Like Nick said, just make sure to balance each Draw/Remove with an InsertInFront and you'll never lose track of a card.

Write functions like "PrintZoneToScreen" and you'll easy be able to send the hands to each player as they're updated.

They key concept to take away is that all the real memory work is now being done by storing all of your state information in the "zone" variables, NOT in the actual logic of your code. You'd be surprised at how much this simplifies your tasks once you get used to the idea.
Top

Posted by Nick Gammon   Australia  (23,165 posts)  Bio   Forum Administrator
Date Reply #11 on Sat 12 Mar 2011 05:48 AM (UTC)
Message
Quote:

for each _, c in ipairs(p1discardlist) do


I think you switched languages here a bit, but other than that I agree with everything you said.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


38,689 views.

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.