001    /*
002    Copyright (c) 1996-2009, Damon Hart-Davis
003    All rights reserved.
004    
005    Redistribution and use in source and binary forms, with or without
006    modification, are permitted provided that the following conditions are
007    met:
008    
009      * Redistributions of source code must retain the above copyright
010        notice, this list of conditions and the following disclaimer.
011    
012      * Redistributions in binary form must reproduce the above copyright
013        notice, this list of conditions and the following disclaimer in the
014        documentation and/or other materials provided with the
015        distribution.
016    
017    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028    */
029    
030    package org.hd.d.pg2k.ai.scorer;
031    
032    import org.hd.d.pg2k.svrCore.MemoryTools;
033    import org.hd.d.pg2k.svrCore.Rnd;
034    
035    /**Immutable (int) Scorer parameter.
036     * We make the class Internable to control instance count and thus save some memory
037     * for at least some values.
038     */
039    public final class ScorerParamInteger implements ScorerParam,
040                                                     MemoryTools.Internable
041        {
042        /**Get instance with the default value, no bias and a unit delta; never null.
043         * Always intern()ed since we assume that there will be few distinct 'default' values.
044         */
045        public static ScorerParamInteger createScorerParamInteger(final int min, final int def, final int max, final String name)
046            { return(MemoryTools.intern(new ScorerParamInteger(min, def, max, name))); }
047    
048        /**Get instance with the default value, and specified bias and delta; never null.
049         * Always intern()ed since we assume that there will be few distinct 'default' values.
050         */
051        public static ScorerParamInteger createScorerParamInteger(final int min, final int def, final int max, final int delta, final boolean biasedLow, final String name)
052            { return(MemoryTools.intern(new ScorerParamInteger(min, def, max, delta, biasedLow, name))); }
053    
054        /**Get instance with the default value, and specified bias and delta; never null.
055         * Only intern()s values close to the default since we assume that there will be relatively few distinct values;
056         * this means that we will likely capture/cache all values of small-range parameters.
057         * We use a similar notion of 'close' to that in the Integer (etc) autoboxing mechanism.
058         */
059        public static ScorerParamInteger createScorerParamInteger(final int min, final int def, final int max, final int delta, final boolean biasedLow, final String name, final int value)
060            {
061            final ScorerParamInteger result = new ScorerParamInteger(min, def, max, delta, biasedLow, name, value);
062            if(Math.abs(def - value) < 128) { return(MemoryTools.intern(result)); }
063            return(result); // Don't try to intern() this value.
064            }
065    
066        /**Construct with all components, validated and stored as-is. */
067        private ScorerParamInteger(final int min, final int def, final int max,
068                                  final int delta, final boolean biasedLow,
069                                  final String name, final int value)
070            {
071            if(!AbstractScorer.isValidParameterName(name)) { throw new IllegalArgumentException(); }
072            if((min > def) || (def > max)) { throw new IllegalArgumentException(); }
073            if((value < min) || (value > max)) { throw new IllegalArgumentException(); }
074            if(delta <= 0) { throw new IllegalArgumentException(); }
075            this.name = name;
076            this.value = value;
077            this.min = min;
078            this.def = def;
079            this.max = max;
080            this.delta = delta;
081            this.biasedLow = biasedLow;
082            }
083    
084        /**Construct with the default value. */
085        private ScorerParamInteger(final int min, final int def, final int max,
086                                  final int delta, final boolean biasedLow,
087                                  final String name)
088            { this(min, def, max, delta, biasedLow, name, def); }
089    
090        /**Construct with the default value, no bias and a unit delta. */
091        private ScorerParamInteger(final int min, final int def, final int max,
092                                  final String name)
093            { this(min, def, max, 1, false, name, def); }
094    
095        /**Get the parameter name; never null or empty. */
096        public String getName() { return(name); }
097    
098        /**Parameter name with allowed syntax as for Java variable name; never null. */
099        public final String name;
100        /**Parameter value in inclusive/closed range [min,max]. */
101        public final int value;
102    
103        /**Minimum legal value for this parameter; no larger than max. */
104        public final int min;
105        /**Default value for this parameter; no smaller than min, no larger than max. */
106        public final int def;
107        /**Maximum legal value for this parameter; no smaller than min. */
108        public final int max;
109    
110        /**The size of a small change during mutation; strictly positive. */
111        public final int delta;
112        /**If true, mutations are biased towards reducing the parameter value. */
113        public final boolean biasedLow;
114    
115        /**Perturb value, usually by a small amount.
116         * We occasionally randomise completely to help a Scorer escape local minima.
117         * We do this with a frequency proportional to the time it would take
118         * to traverse the entire permitted range one delta at a time.
119         *
120         * @see org.hd.d.pg2k.ai.scorer.ScorerParam#perturb()
121         */
122        public ScorerParam perturb()
123            {
124            // We only attempt to make big leaps if the entire range
125            // could be traversed in a reasonable number of deltas in the same direction...
126            // We change to a new completely random value
127            // somewhat less often than that minimum
128            // though MUCH more frequently than a true random walk would take.
129            final long range = 1 + (max - (long) min);
130            if(range <= Integer.MAX_VALUE)
131                {
132                final long minDeltas = (delta == 1) ? range : (range / delta);
133                if(minDeltas < (Integer.MAX_VALUE >>> 3))
134                    {
135                    if(Rnd.fastRnd.nextInt(((int)minDeltas) << 2) == 0)
136                        {
137                        final int newValue = min + Rnd.goodRnd.nextInt((int) range);
138                        // Return existing value if not actually different to save a little heap churn.
139                        if(newValue == value) { return(this); }
140                        return(createScorerParamInteger(min, def, max, delta, biasedLow, name, Math.max(min, Math.min(max, newValue))));
141                        }
142                    }
143                }
144    
145            final boolean down = (!biasedLow) ? Rnd.goodRnd.nextBoolean() :
146                (Rnd.goodRnd.nextInt(3) != 0); // Apply bias.
147            // For delta > 1, pick random value up to given delta.
148            final int deltaToUse = (delta == 1) ? delta : (Rnd.goodRnd.nextInt(delta)+1);
149            final int newValue = down ? (value-deltaToUse) : (value+deltaToUse);
150            // Return existing value if not actually different to save a little heap churn.
151            if(newValue == value) { return(this); }
152            return(createScorerParamInteger(min, def, max, delta, biasedLow, name, Math.max(min, Math.min(max, newValue))));
153            }
154    
155    
156        /**Parse the String representation of a parameter returning a value of the same type; never null.
157         * If the argument is null or unparsable or out of range,
158         * then the result has the default value,
159         * else an instance with the requested parameter value is returned.
160         * @return legal value of the same type as this, with the parsed input String's value if possible
161         */
162        public ScorerParam parse(final String v)
163            {
164            int parsedValue = def; // Use default value unless the parse is successful.
165            if(v != null)
166                {
167                try
168                    {
169                    final int p = Integer.parseInt(v);
170                    if((p >= min) && (p <= max)) { parsedValue = p; }
171                    }
172                catch(final NumberFormatException e) { /* Ignore. */ }
173                }
174    
175            // Memory optimisation where value is the same as for the current instance.
176            if(parsedValue == value) { return(this); }
177    
178            // Return instance containing the parsed value.
179            return(createScorerParamInteger(min, def, max, delta, biasedLow, name, parsedValue));
180            }
181    
182        /**Use the supplied parameter if possible (correct name/type/bounds), else return the default; never null.
183         * We return this to avoid creating a new instance whenever possible.
184         */
185        public ScorerParam extract(final ScorerParam p)
186            {
187            if((p == this) || equals(p)) { return(this); }
188            if(p.getClass() != getClass()) { return(createScorerParamInteger(min, def, max, delta, biasedLow, name)); }
189            final ScorerParamInteger pi = (ScorerParamInteger) p;
190            // Return default if this is not identical in every particular except the value.
191            if((pi.def != def) || !name.equals(pi.name) ||
192               (pi.min != min) || (pi.max != max) ||
193               (pi.delta != delta) || (pi.biasedLow != biasedLow))
194                { return(createScorerParamInteger(min, def, max, delta, biasedLow, name)); }
195            return(pi);
196            }
197    
198    
199        /**Generate the "name=value" text; never null or empty, though the value part may be empty. */
200        public String toNameValueString()
201            {
202            final StringBuilder sb = new StringBuilder(12 + name.length());
203            sb.append(name).append('=').append(value);
204            return(sb.toString());
205            }
206    
207        /**Human-readable representation. */
208        @Override
209        public String toString() { return(toNameValueString()); }
210    
211    
212        /* (non-Javadoc)
213         * @see org.hd.d.pg2k.ai.scorer.ScorerParam#similar(org.hd.d.pg2k.ai.scorer.ScorerParam)
214         */
215        public boolean similar(final ScorerParam p)
216            {
217            if(p == this) { return(true); }
218            if(!(p instanceof ScorerParamInteger)) { return(false); }
219            // Treat as different if different name or bounds.
220            final ScorerParamInteger pi = (ScorerParamInteger) p;
221            if((max != pi.max) || (min != pi.min) || (def != pi.def) || (delta != pi.delta) ||
222               !name.equals(pi.name))
223                { return(false); }
224            // Similar if the values are within one delta either way.
225            return(Math.abs(pi.value - value) <= delta);
226            }
227    
228    
229        /**The hash depends on the name and the value as the most variable components. */
230        @Override public int hashCode() { return(name.hashCode() ^ value); }
231    
232        /**Equality depends on all fields. */
233        @Override public boolean equals(final Object obj)
234            {
235            if(this == obj) {
236                return true;
237                }
238            if(obj == null) {
239                return false;
240                }
241            if(getClass() != obj.getClass()) {
242                return false;
243                }
244            final ScorerParamInteger other = (ScorerParamInteger) obj;
245            if(value != other.value) {
246                return false;
247                }
248            if(biasedLow != other.biasedLow) {
249                return false;
250                }
251            if(def != other.def) {
252                return false;
253                }
254            if(delta != other.delta) {
255                return false;
256                }
257            if(max != other.max) {
258                return false;
259                }
260            if(min != other.min) {
261                return false;
262                }
263            if(!name.equals(other.name)) {
264                return false;
265                }
266            return true;
267            }
268        }