Poker-AI.org
http://poker-ai.org/phpbb/

HUNL Preflop toy games
http://poker-ai.org/phpbb/viewtopic.php?f=24&t=2685
Page 1 of 1

Author:  Pitt [ Tue Jan 21, 2014 6:58 pm ]
Post subject:  HUNL Preflop toy games

Hi !

I'm playing with my preflop HUNL nash equilibrium engine and I would like to check my numbers.
The game values I get don't match the simulation of DeuceBuster here http://forumserver.twoplustwo.com/15/po ... ndex2.html .

For push/fold 8bb, I find -0.0113527 BB for the small blind player (ie 0.488647 adding the SB = 0.5BB)
DeuceBuster gets 0.49586253754872656.

And for limp/jam/fold (with limp/check = showdown restricted to pot) I get something like -0.00519 (ie 0.49481)
DeuceBuster gets 0.50060893618890523

Could someone please tell me his results at least for the push/fold game please ?

Thanks !

Author:  Pitt [ Tue Jan 21, 2014 7:08 pm ]
Post subject:  Re: HUNL Preflop toy games

(If you have other push/fold reliable game values, I can compare to them to check if my framework makes some error...)

Author:  jukofyork [ Wed Jan 22, 2014 3:03 am ]
Post subject:  Re: HUNL Preflop toy games

Code:
NE Push [R=8]:
A     K     Q     J     T     9     8     7     6     5     4     3     2
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 A
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 K
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 Q
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 J
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000 0.000 T
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000 0.000 0.000 9
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000 0.000 0.000 8
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000 0.000 7
1.000 1.000 1.000 0.000 0.000 0.000 0.000 1.000 1.000 1.000 1.000 0.000 0.000 6
1.000 1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 1.000 1.000 0.000 5
1.000 1.000 0.192 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.000 0.000 4
1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.000 3
1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 2

NE Call [R=8]:
A     K     Q     J     T     9     8     7     6     5     4     3     2
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 A
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 K
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000 0.000 Q
1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000 0.000 0.000 0.000 0.000 J
1.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 T
1.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 9
1.000 1.000 1.000 0.000 0.000 0.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 8
1.000 1.000 0.528 0.000 0.000 0.000 0.000 1.000 0.000 0.000 0.000 0.000 0.000 7
1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.000 0.000 0.000 0.000 6
1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.000 0.000 0.000 5
1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.000 0.000 4
1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 0.000 3
1.000 1.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 2

SB's EV: -0.0041376 BB/hand (Push=61.8629%)
BB's EV: 0.00413759 BB/hand (Call=44.973%)


and: -0.0041376 + 0.5 = 0.4958624.

Computed using 100k iterations of fictitious play:
Code:
#define RAKE 0.0 // 0.5=1BB from the 20BB pot (ie: 0.5 each, so if tie we get back 9.5BB).

// Use this to calculate the push/call matrices for a given R.
void RunFictitiousPlay(const double RRatio,const int MaxIters,const double UpdateRate,
                   double* PushMatrix,double* CallMatrix)
{
   // These are the temp matrices that hold the final NE strategy.
   double TempPushMatrix[169];
   double TempCallMatrix[169];

   // Init the matrices to 1.0 for all values.
   // NOTE: Either 0.5 or 1 seems to converge at about the same rate (0.0 was very slow...)
   for (int I=0;I<169;I++) {
      PushMatrix[I]=1.0;
      CallMatrix[I]=1.0;
   }

   // Keep running the fictitious play for a set number of iterations.
   for (int Iter=0;Iter<MaxIters;Iter++) {

      // If we have been passed UpdateRate=0.0 then use true game theory method, else just
      // use UpdateRate as the weight.
      double WeightNew=(UpdateRate<EPSILON?(1.0/(double)(Iter+1)):UpdateRate);

      // Update SB strategy...
      // For each possible hand work out if it is +EV or -EV to call vs the main matrix.
      for (int I=0;I<169;I++) {

         // Init EV_Sum to 0 for calling with this hand.
         double EV_Push=0.0;

         // This is the sum of hole multiplicity values.
         double NormalizationSum=0.0;

         // Work out the EV vs each of the opposing hands.
         for (int J=0;J<169;J++) {
               
            // EV when we push and he don't fold.
            EV_Push+=CallMatrix[J]*(double)HoleMultiplicityIfHolding[I][J]*(((2.0*(RRatio+RAKE)*PWin[I][J])+((RRatio+RAKE)*PTie[I][J]))-RRatio);

            // EV when we push and he folds.
            EV_Push+=(1.0-CallMatrix[J])*(double)HoleMultiplicityIfHolding[I][J]*1.0;

            // Keep a total of the sums so we can use to normalize after.
            NormalizationSum+=(double)HoleMultiplicityIfHolding[I][J];

         }

         // Normalize the EV_Push value.
         EV_Push/=NormalizationSum;

         // If the sum of EV is +ve then we should play this hand, else we shouldn't.
         if (EV_Push>(-0.5))
            TempPushMatrix[I]=1.0;
         else
            TempPushMatrix[I]=0.0;

      }

      // Update the push matrix.
      // NOTE: We tend to get better convergence if we replace this before updating the BB strategy below.
      for (int I=0;I<169;I++)
         PushMatrix[I]=((1.0-WeightNew)*PushMatrix[I]) + (WeightNew*TempPushMatrix[I]);

      // Update BB strategy...
      // For each possible hand work out if it is +EV or -EV to call vs the main matrix.
      for (int I=0;I<169;I++) {

         // Init EV_Sum to 0 for calling with this hand.
         double EV_Call=0.0;

         // This is the sum of hole multiplicity values.
         double NormalizationSum=0.0;

         // Work out the EV vs each of the opposing hands.
         for (int J=0;J<169;J++) {
               
            // EV when he pushes and we call.
            EV_Call+=PushMatrix[J]*(double)HoleMultiplicityIfHolding[I][J]*(((2.0*(RRatio+RAKE)*PWin[I][J])+((RRatio+RAKE)*PTie[I][J]))-RRatio);

            // Keep a total of the sums so we can use to normalize after.
            NormalizationSum+=PushMatrix[J]*(double)HoleMultiplicityIfHolding[I][J];

         }

         // Normalize the EV_Push value.
         EV_Call/=NormalizationSum;

         // If the sum of EV is +ve then we should play this hand, else we shouldn't.
         if (EV_Call>(-1.0))
            TempCallMatrix[I]=1.0;
         else
            TempCallMatrix[I]=0.0;

      }

      // Update the call matrix.
      for (int I=0;I<169;I++)
         CallMatrix[I]=((1.0-WeightNew)*CallMatrix[I]) + (WeightNew*TempCallMatrix[I]);

   }

} // End RunFictitiousPlay.

(just set the RAKE constant to zero - that was just a test to see how it changed things).

I haven't got code to run limp/jam/fold, but can easily write some if you still haven't figured out your problem by tomorrow (I'm about to crash out and likely to make a silly mistake with the if I try to write it now sorry...).

Here's the code for the minraise/jam/fold game:

Code:
#define RAISE_PLAY_PENALTY 0.0            // The amount of EV in BBs we penalize making a raise play (to force push is near EV).

// Use this to calculate the matrices for a given R.
void RunFictitiousPlayRaisePlay(const double RRatio,const int MaxIters,const double UpdateRate,
                        double* PushMatrix, double* RaiseCallMatrix,double* RaiseFoldMatrix,
                        double* CallPushMatrix,double* PushOverRaiseMatrix)
{
   // These are the temp matrices that hold the final NE strategy.
   double TempPushMatrix[169];
   double TempRaiseCallMatrix[169];
   double TempRaiseFoldMatrix[169];
   double TempCallPushMatrix[169];
   double TempPushOverRaiseMatrix[169];

   // Init the matrices for all values.
   for (int I=0;I<169;I++) {
      PushMatrix[I]=0.0;//0.5;
      RaiseCallMatrix[I]=0.0;//0.25;
      RaiseFoldMatrix[I]=0.0;//0.25;
      CallPushMatrix[I]=0.0; //1.0;
      PushOverRaiseMatrix[I]=0.0; //1.0;
   }

   // Keep running the fictitious play for a set number of iterations.
   for (int Iter=0;Iter<MaxIters;Iter++) {

      // If we have been passed UpdateRate=0.0 then use true game theory method, else just use UpdateRate as the weight.
      double WeightNew=(UpdateRate<EPSILON?(1.0/(double)(Iter+1)):UpdateRate);

      // Update SB's strategy.
      for (int I=0;I<169;I++) {

         // Init EV_Sum to 0 for each option.
         double EV_Push=0.0;
         double EV_RaiseCall=0.0;
         double EV_RaiseFold=0.0;

         // This is the sum of hole multiplicity values.
         double NormalizationSum=0.0;

         // Work out the EV vs each of the opposing hands.
         for (int J=0;J<169;J++) {
               
            // EV when we push and he does/doesn't fold.
            EV_Push+=(1.0-CallPushMatrix[J])*(double)HoleMultiplicityIfHolding[I][J]*1.0;
            EV_Push+=CallPushMatrix[J]*(double)HoleMultiplicityIfHolding[I][J]*(((2.0*RRatio*PWin[I][J])+(RRatio*PTie[I][J]))-RRatio);

            // EV when we raise/call and he does/doesn't fold.
            EV_RaiseCall+=(1.0-PushOverRaiseMatrix[J])*(double)HoleMultiplicityIfHolding[I][J]*1.0;
            EV_RaiseCall+=PushOverRaiseMatrix[J]*(double)HoleMultiplicityIfHolding[I][J]*(((2.0*RRatio*PWin[I][J])+(RRatio*PTie[I][J]))-RRatio);

            // EV when we raise/fold and he does/doesn't fold.
            EV_RaiseFold+=(1.0-PushOverRaiseMatrix[J])*(double)HoleMultiplicityIfHolding[I][J]*1.0;
            EV_RaiseFold+=PushOverRaiseMatrix[J]*(double)HoleMultiplicityIfHolding[I][J]*(-2.0);

            // Keep a total of the sums so we can use to normalize after.
            NormalizationSum+=(double)HoleMultiplicityIfHolding[I][J];

         }

         // Normalize the EV values.
         EV_Push/=NormalizationSum;
         EV_RaiseCall/=NormalizationSum;
         EV_RaiseFold/=NormalizationSum;

         // Init all to zero ready to set below if found to be +EV.
         TempPushMatrix[I]=0.0;
         TempRaiseCallMatrix[I]=0.0;
         TempRaiseFoldMatrix[I]=0.0;

         // If the sum of EV for the best option is +ve then we should play this hand, else we shouldn't.
         if (EV_Push>=(Max(EV_RaiseFold,EV_RaiseCall)-RAISE_PLAY_PENALTY)) {
            if (EV_Push>(-0.5))
               TempPushMatrix[I]=1.0;
         }
         else if (EV_RaiseCall>=Max(EV_Push,EV_RaiseFold)) {
            if (EV_RaiseCall>(-0.5))
               TempRaiseCallMatrix[I]=1.0;   
         }
         else { //if (EV_RaiseFold>=(Max(EV_Push,EV_RaiseCall)-EPSILON)) {
            if (EV_RaiseFold>(-0.5))
               TempRaiseFoldMatrix[I]=1.0;   
         }

      }

      // Update the SB's matrices.
      // NOTE: We tend to get better convergence if we replace this before updating the BB strategy below.
      for (int I=0;I<169;I++) {
         PushMatrix[I]=((1.0-WeightNew)*PushMatrix[I])+(WeightNew*TempPushMatrix[I]);
         RaiseCallMatrix[I]=((1.0-WeightNew)*RaiseCallMatrix[I])+(WeightNew*TempRaiseCallMatrix[I]);
         RaiseFoldMatrix[I]=((1.0-WeightNew)*RaiseFoldMatrix[I])+(WeightNew*TempRaiseFoldMatrix[I]);
      }

      // Update BB strategy vs a push.
      for (int I=0;I<169;I++) {

         // Init EV sums 0 for calling with this hand.
         double EV_CallPush=0.0;

         // This is the sum of hole multiplicity values.
         double NormalizationSum=0.0;

         // Work out the EV vs each of the opposing hands.
         for (int J=0;J<169;J++) {
               
            // EV when he pushes and we call.
            EV_CallPush+=PushMatrix[J]*(double)HoleMultiplicityIfHolding[I][J]*(((2.0*RRatio*PWin[I][J])+(RRatio*PTie[I][J]))-RRatio);

            // Keep a total of the sums so we can use to normalize after.
            NormalizationSum+=PushMatrix[J]*(double)HoleMultiplicityIfHolding[I][J];

         }

         // Normalize the EV_Push value.
         EV_CallPush/=NormalizationSum;

         // If the sum of EV is +ve then we should play this hand, else we shouldn't.
         if (EV_CallPush>(-1.0))
            TempCallPushMatrix[I]=1.0;
         else
            TempCallPushMatrix[I]=0.0;

      }

      // Update the call push matrix.
      for (int I=0;I<169;I++)
         CallPushMatrix[I]=((1.0-WeightNew)*CallPushMatrix[I]) + (WeightNew*TempCallPushMatrix[I]);

      // ---------------------

      // Update BB strategy vs a raise.
      for (int I=0;I<169;I++) {

         // Init EV sums 0 for calling with this hand.
         double EV_PushOverRaise=0.0;

         // This is the sum of hole multiplicity values.
         double NormalizationSum=0.0;

         // Work out the EV vs each of the opposing hands.
         for (int J=0;J<169;J++) {
               
            // EV when he raises, we push over him and he folds/calls.
            EV_PushOverRaise+=RaiseCallMatrix[J]*(double)HoleMultiplicityIfHolding[I][J]*(((2.0*RRatio*PWin[I][J])+(RRatio*PTie[I][J]))-RRatio);
            EV_PushOverRaise+=RaiseFoldMatrix[J]*(double)HoleMultiplicityIfHolding[I][J]*2.0;

            // Keep a total of the sums so we can use to normailize after.
            NormalizationSum+=(RaiseCallMatrix[J]+RaiseFoldMatrix[J])*(double)HoleMultiplicityIfHolding[I][J];

         }

         // Normalize the EV_Push value.
         EV_PushOverRaise/=NormalizationSum;

         // If the sum of EV is +ve then we should play this hand, else we shouln't.
         if (EV_PushOverRaise>(-1.0))
            TempPushOverRaiseMatrix[I]=1.0;
         else
            TempPushOverRaiseMatrix[I]=0.0;

      }

      // Update the push-over matrix.
      for (int I=0;I<169;I++)
         PushOverRaiseMatrix[I]=((1.0-WeightNew)*PushOverRaiseMatrix[I]) + (WeightNew*TempPushOverRaiseMatrix[I]);

   }

} // End RunFictitiousPlayRaisePlay.

(again, ignore RAISE_PLAY_PENALTY - that was just me fiddling to see what effect it had)

From experience, my best guess of what would cause a small error in EV like you have is something to do with card-removal (see the use of HoleMultiplicityIfHolding[][] in my code).

Anyway, hope this helps.

Juk :)

Author:  Pitt [ Wed Jan 22, 2014 10:22 am ]
Post subject:  Re: HUNL Preflop toy games

Thanks jukofyork :)

My engine is a CS-CFRM one, taking any interfaced game as input.
I'm 99,999% sure it doesn't fail, so I figure you're right, it may be the hole cards distribution.
I planed to rewrite my old dirty cards distribution code, I definitely will do it soon !

Thanks again !

Author:  Pitt [ Wed Jan 22, 2014 12:52 pm ]
Post subject:  Re: HUNL Preflop toy games

...re-generating the preflop all-in equities to match my new hole cards indexing...
Takes a while (at least 8 coffees)...

Author:  Pitt [ Wed Jan 22, 2014 3:49 pm ]
Post subject:  Re: HUNL Preflop toy games

Hmmm, doesn't look it's that, always getting a strange value.

I really think my drawing probabilities are good :
Code:
Hand  ---- proba for sb ---- proba for bb when sb drawed 22
22 0.004524886877828055 8.169934640522876E-4
32o 0.00904977375565611 0.004901960784313725
42o 0.00904977375565611 0.004901960784313725
52o 0.00904977375565611 0.004901960784313725
62o 0.00904977375565611 0.004901960784313725
72o 0.00904977375565611 0.004901960784313725
82o 0.00904977375565611 0.004901960784313725
92o 0.00904977375565611 0.004901960784313725
T2o 0.00904977375565611 0.004901960784313725
J2o 0.00904977375565611 0.004901960784313725
Q2o 0.00904977375565611 0.004901960784313725
K2o 0.00904977375565611 0.004901960784313725
A2o 0.00904977375565611 0.004901960784313725
32s 0.0030165912518853697 0.0016339869281045752
33 0.004524886877828055 0.004901960784313725
43o 0.00904977375565611 0.00980392156862745
53o 0.00904977375565611 0.00980392156862745
63o 0.00904977375565611 0.00980392156862745
73o 0.00904977375565611 0.00980392156862745
83o 0.00904977375565611 0.00980392156862745
93o 0.00904977375565611 0.00980392156862745
T3o 0.00904977375565611 0.00980392156862745
J3o 0.00904977375565611 0.00980392156862745
Q3o 0.00904977375565611 0.00980392156862745
K3o 0.00904977375565611 0.00980392156862745
A3o 0.00904977375565611 0.00980392156862745
42s 0.0030165912518853697 0.0016339869281045752
43s 0.0030165912518853697 0.0032679738562091504
44 0.004524886877828055 0.004901960784313725
54o 0.00904977375565611 0.00980392156862745
...

I'll recheck my all-in equities but they look good too...
Don't want to bother you, I'll keep on debugging!..
Thanks for giving me the right value for push/fold so that I'm not completely in the dark ;)

Author:  Pitt [ Fri Jan 24, 2014 3:30 pm ]
Post subject:  Re: HUNL Preflop toy games

After searching many hours...

My best-responses values converge to the expected ones, so my strategies are ok, it's only my utility calculus that fails.

As I don't find any programing mistake, I guess my picture of the CS-CFRM algorithm is wrong, certainly with utility backpassing.
I wrote it in a pseudo-code and recursive version to expose the more clearly how I think it sould be.
Does someone identify a basic mistake there ?
(please guess everything is well done in not-implemented methods 8-) )

Code:

// Method I call on the game tree root that does one CSCFRM iteration
// And returns the utility for this one. The realizationWeights is initialized with "one" values.

double[] iter(GameNode node, double[] realizationWeights){
   // Terminal node, utility = payoffs
   if(node.type == PAYOFFS)
      return node.getPayoffs();
      
   // Chance sampled, so we just chose a random child according to chances weights,
   //   pass current realization weights and get utility back
   if(node.type == CHANCE)
      return iter(node.choseRandomChanceChild(), realizationWeights);
      
   // Player node
   double[][] allActionsUtil = new double[node.getNbActions()][];
   
   // Compute current strategy based on regret
   // and update strategy sum
   node.updateStrategies(realizationWeights[node.getPlayer()]);
   for(Action action : node.getPossibleActions()){
      double[] real = realizationWeights.copy();
      real[node.getPlayer()] *= node.getCurrentStrategy[action];
      double[] actionUtil = iter(node.getPlayerActionChild(action),  real);
      for(int p = 0; p < nbPlayers; p++)
         util[p] +=  node.getStrategy()[action] * actionUtil[p]);
      allActionsUtil[action] = actionUtil;
   }
   // Compute regrets for this iteration and add them to the regret sums
   node.computeRegret(allActionsUtil);
   return util;
}

Author:  Pitt [ Sat Jan 25, 2014 7:16 pm ]
Post subject:  Re: HUNL Preflop toy games

Closing this topic, I rediscovered how doubles can cause such trouble when one doesn't care about the little error in each operation :P In four days :|

Author:  NaHibi08 [ Fri Aug 08, 2014 3:35 pm ]
Post subject:  Re: HUNL Preflop toy games

BB's EV: 0.00413759 BB/hand (Call=44.973%)

How can I calculate the BB's EV?
I always get a wrong result :/

I have the right matrices, SB's EV and Push/Call %

Please help me.

Author:  NaHibi07 [ Mon Sep 29, 2014 10:23 am ]
Post subject:  Re: HUNL Preflop toy games

Quote:
// If we have been passed UpdateRate=0.0 then use true game theory method, else just
// use UpdateRate as the weight.
double WeightNew=(UpdateRate<EPSILON?(1.0/(double)(Iter+1)):UpdateRate);


Hi, I have a problem with the WeightNew. The results of the available calculators like HRC, SimpleNash are all equal. I think the available calculators uses all the same WeightNew.

I tried fixed values for WeightNew and variable values for WeightNew like the one above but i sometimes get a slightly different result.
currentEPSILON = abs(evPush) + abs(evCall) and UpdateRate is a fix value, right?

Does somebody know how the weighting works?

Author:  jukofyork [ Mon Sep 29, 2014 10:33 am ]
Post subject:  Re: HUNL Preflop toy games

NaHibi07 wrote:
Quote:
// If we have been passed UpdateRate=0.0 then use true game theory method, else just
// use UpdateRate as the weight.
double WeightNew=(UpdateRate<EPSILON?(1.0/(double)(Iter+1)):UpdateRate);


Hi, I have a problem with the WeightNew. The results of the available calculators like HRC, SimpleNash are all equal. I think the available calculators uses all the same WeightNew.

I tried fixed values for WeightNew and variable values for WeightNew like the one above but i sometimes get a slightly different result.
currentEPSILON = abs(evPush) + abs(evCall) and UpdateRate is a fix value, right?

Does somebody know how the weighting works?

If you use 1/n (ie: set UpdateRate=0.0 in the above code), then given enough iterations it will eventually converge to the NE (barring floating point errors, etc).

If you use a small fixed constant instead (ie: set UpdateRate>0.0 in the above code) then it isn't guaranteed to converge to a NE, but given a carefully chosen value; it often will be very close to the NE and converge MUCH faster than using 1/n.

You can also do stuff like quickly get close to a NE using a constant value, then use this a new starting point using 1/n, etc.

See post #14 onwards in this thread for a better explanation:

http://forumserver.twoplustwo.com/15/po ... u-1418507/

Juk :)

Author:  NaHibi07 [ Mon Sep 29, 2014 3:45 pm ]
Post subject:  Re: HUNL Preflop toy games

thx jukofyork, now it works :)

Page 1 of 1 All times are UTC
Powered by phpBB® Forum Software © phpBB Group
http://www.phpbb.com/