All Downloads are FREE. Search and download functionalities are using the official Maven repository.

codes.derive.foldem.Range Maven / Gradle / Ivy

/*
 * This file is part of Fold'em, a Java library for Texas Hold 'em Poker.
 *
 * Fold'em is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fold'em is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Fold'em.  If not, see .
 */
package codes.derive.foldem;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import codes.derive.foldem.util.RandomContext;

/**
 * A type representing a range of hands.
 */
public class Range {

	/*
	 * A map containing weighted hands in lists mapped to their respective
	 * weights.
	 */
	private final Map> weighted = new HashMap<>();

	/* A list containing hands that always appear within this group. */
	private final List constant = new ArrayList<>();

	/**
	 * Defines a {@link Hand} in this {@link Range}.
	 * 
	 * @param hand
	 *            The hand.
	 * @return The {@link Range} context, for chaining.
	 */
	public Range define(Hand hand) {
		if (contains(hand)) {
			System.out.println(hand);
			throw new IllegalArgumentException("Hand already exists within range");
		}
		constant.add(hand);
		return this;
	}

	/**
	 * Defines a series of hands in this {@link Range}.
	 * 
	 * @param hands
	 *            The hands.
	 * @return The {@link Range} context, for chaining.
	 */
	public Range define(Hand... hands) {
		for (Hand hand : hands) {
			define(hand);
		}
		return this;
	}

	/**
	 * Defines a {@link java.util.Collection } of hands in this {@link Range}.
	 * 
	 * @param hands
	 *            The hands
	 * @return The {@link Range} context, for chaining.
	 */
	public Range define(Collection hands) {
		return define(hands.toArray(new Hand[0]));
	}

	/**
	 * Defines a weighted {@link Hand} in this range.
	 * 
	 * 

* The weight of a {@link Hand} defines how often it should be used instead * of skipped by {@link Range#sample(Random)}. A {@link Hand} with a weight * of 1.0 will always be used, 0.5 half the time, 0.0 never. *

* * @param weight * The weight, as a decimal. This will define how often the * specified hand should appear in the range. * @param hand * The hand. * @return The {@link Range} context, for chaining. */ public Range define(double weight, Hand hand) { if (weight <= 0.0 || weight > 1.0) { throw new IllegalArgumentException("Weight out of bounds"); } for (List hands : weighted.values()) { if (hands.contains(hand)) { hands.remove(hand); } } if (!weighted.containsKey(weight)) { weighted.put(weight, new ArrayList()); } List hands = weighted.get(weight); if (!hands.contains(hand)) { hands.add(hand); } return this; } /** * Defines the specified weighted hands in this range. * *

* The weight of a {@link Hand} defines how often it should be used instead * of skipped by {@link Range#sample(Random)}. A {@link Hand} with a weight * of 1.0 will always be used, 0.5 half the time, 0.0 never. *

* * @param weight * The weight, as a decimal. This will define how often the * specified hands will appear in the range on a call to * {@link Range#sample()}. * @param hands * The hands. * @return The {@link Range} context, for chaining. */ public Range define(double weight, Hand... hands) { for (Hand hand : hands) { define(weight, hand); } return this; } /** * Defines the specified weighted hands in this range. * *

* The weight of a {@link Hand} defines how often it should be used instead * of skipped by {@link Range#sample(Random)}. A {@link Hand} with a weight * of 1.0 will always be used, 0.5 half the time, 0.0 never. *

* * @param weight * The weight, as a decimal. This will define how often the * specified hands will appear in the range. * @param hands * The hands. * @return The {@link Range} context, for chaining. */ public Range define(double weight, Collection hands) { return define(weight, hands.toArray(new Hand[0])); } /** * Obtains whether or not the specified {@link Hand} can appear * within this range. * * @param hand * The hand. * @return The {@link Range} context, for chaining. */ public boolean contains(Hand hand) { for (List hands : weighted.values()) { if (hands.contains(hand)) { return true; } } return constant.contains(hand); } /** * Obtains the frequency at which the specified {@link Hand} will * appear within this {@link Range}, as a decimal. * * @param hand * The hand. * @return The frequency at which the specified {@link Hand} will * appear within this {@link Range}, as a decimal. */ public double weight(Hand hand) { for (double weight : weighted.keySet()) { if (weighted.get(weight).contains(hand)) { return weight; } } return constant.contains(hand) ? 1.0 : 0; } /** * Obtains a {@link Hand} from this {@link Range}, excluding weighted hands * at their associated frequencies. * * @param random * The random context to use to generate random numbers for * deciding whether or not to include a specific weighted hand. * @return The sampled {@link Hand}. */ public Hand sample(Random random) { /* * Make sure we're going to have enough hands to sample at correct * frequencies. */ double weightTotal = 1.0 * constant.size(); for (Double weight : weighted.keySet()) { weightTotal += weight; } if (weightTotal < 1.0) { throw new IllegalStateException("Too few hands for accurate sample"); } /* * Create a list of candidate hands containing hands with constant even * weight. */ List candidates = constant; /* * Determine whether or not to use a weighted subset of hands. */ double p = Math.random(), c = 0.0; for (double w : weighted.keySet()) { c += w; if (p <= c) { candidates = weighted.get(w); break; } } /* * Return a random hand from our group of candidates. */ return candidates.get(random.nextInt(candidates.size())); } /** * Obtains a {@link Hand} from this {@link Range}, excluding * weighted hands at their correct frequencies. * *

* uses the random number generator specified in {@link RandomContext}. *

* * @return The sampled {@link Hand}. */ public Hand sample() { return sample(RandomContext.get()); } /** * Obtains an unmodifiable view containing all hands within this * {@link Range} including weighted hands. * * @return An unmodifiable view containing all hands within this * {@link Range}. */ public Collection all() { List hands = new ArrayList<>(); hands.addAll(constant); for (List l : weighted.values()) { hands.addAll(l); } return Collections.unmodifiableCollection(hands); } @Override public String toString() { StringBuilder bldr = new StringBuilder().append(Range.class.getName()); bldr.append("["); for (Hand hand : constant) { bldr.append(hand); bldr.append(","); } for (double weight : weighted.keySet()) { bldr.append(weighted.get(weight)).append(" ").append(weight); bldr.append(","); } bldr.deleteCharAt(bldr.length() - 1); return bldr.append("]").toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + constant.hashCode(); result = prime * result + weighted.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Range other = (Range) obj; if (!constant.containsAll(other.constant)) { return false; } else if (!weighted.equals(other.weighted)) return false; return true; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy