source: 2014/24/EemeliK/Zombieland/Jypeli/Meter.cs @ 5974

Revision 5974, 20.8 KB checked in by empaheik, 4 years ago (diff)
Line 
1#region MIT License
2/*
3 * Copyright (c) 2009 University of Jyväskylä, Department of Mathematical
4 * Information Technology.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24#endregion
25
26/*
27 * Authors: Tero Jäntti, Tomi Karppinen, Janne Nikkanen.
28 */
29
30using System;
31using System.Collections.Generic;
32
33namespace Jypeli
34{
35    /// <summary>
36    /// Suunta mittarin muutokselle.
37    /// </summary>
38    public enum TriggerDirection
39    {
40        /// <summary>
41        /// Mittarin arvo kasvaa.
42        /// </summary>
43        Up,
44
45        /// <summary>
46        /// Mittarin arvo vähenee.
47        /// </summary>
48        Down,
49       
50        /// <summary>
51        /// Ei väliä suunnalla (kasvaa tai vähenee).
52        /// </summary>
53        Irrelevant
54    };
55
56    /// <summary>
57    /// Mittari, joka mittaa erityyppisiä arvoja.
58    /// Sidottavissa näyttöihin, kuten <c>ValueDisplay</c> ja <c>BarGauge</c>.
59    /// </summary>
60    public abstract class Meter
61    {
62        /// <summary>
63        /// Mittarin suhteellinen arvo (minimi 0, maksimi 1)
64        /// </summary>
65        public abstract double RelativeValue { get; set; }
66
67        internal abstract double GetValue();
68        internal abstract double GetMinValue();
69        internal abstract double GetMaxValue();
70
71        /// <summary>
72        /// Palauttaa mittarin sen todellisessa muodossa, jotta sen kaikkiin
73        /// jäseniin pääsee käsiksi.
74        /// </summary>
75        /// <typeparam name="T">Tyyppi, jota mittari mittaa</typeparam>
76        /// <returns>Mittari, joka mittaa tyyppiä T</returns>
77        public Meter<T> OfType<T>() where T : struct, IComparable<T>, IEquatable<T>
78        {
79            Type meterType = this.GetType();
80            Type[] genargs = meterType.GetGenericArguments();
81
82            if ( genargs.Length < 1 ) throw new ArgumentException( "This meter is not typed" );
83            if ( genargs[0] != typeof( T ) ) throw new ArgumentException( String.Format( "This meter measures {0}, not {1}", genargs[0].Name, typeof( T ).Name ) );
84
85            return (Meter<T>)this;
86        }
87    }
88
89    /// <summary>
90    /// Mittari, joka mittaa erityyppisiä arvoja.
91    /// Sidottavissa näyttöihin, kuten <c>ValueDisplay</c> ja <c>BarGauge</c>.
92    /// </summary>
93    [Save]
94    public abstract class Meter<ValueType> : Meter where ValueType : struct, IComparable<ValueType>, IEquatable<ValueType>
95    {
96        private struct Trigger
97        {
98            public ValueType value;
99            public TriggerDirection direction;
100            public Action method;
101
102            public Trigger(ValueType value, TriggerDirection direction, Action method)
103            {
104                this.value = value;
105                this.direction = direction;
106                this.method = method;
107            }
108        }
109
110        private bool valueSet;
111
112        private ValueType val;
113        private ValueType minval;
114        private ValueType maxval;
115        private ValueType defval;
116
117        private List<Trigger> triggers;
118
119        /// <summary>
120        /// Mittarin arvo.
121        /// </summary>
122        [Save]
123        public ValueType Value
124        {
125            get { return val; }
126            set
127            {
128                if ( value.Equals( val ) )
129                    return;
130
131                ValueType oldval = val;
132                valueSet = true;
133
134                if (value.CompareTo(minval) < 0) value = minval;
135                if (value.CompareTo(maxval) > 0) value = maxval;
136
137                val = value;
138                OnChange(oldval, value);
139                CheckLimits( oldval, value );
140                CheckTriggers(oldval, value);
141            }
142        }
143
144        /// <summary>
145        /// Mittarin oletusarvo.
146        /// </summary>
147        public ValueType DefaultValue
148        {
149            get { return defval; }
150            set
151            {
152                defval = clampValue( value, minval, maxval );
153                if ( !valueSet ) Value = value;
154            }
155        }
156
157        /// <summary>
158        /// Mittarin pienin sallittu arvo.
159        /// Kun mittari saavuttaa tämän arvon, laukeaa tapahtuma <c>LowerLimit</c>.
160        /// </summary>
161        public ValueType MinValue
162        {
163            get { return minval; }
164            set { minval = value; updateBounds(); }
165        }
166
167        /// <summary>
168        /// Mittarin suurin sallittu arvo.
169        /// Kun mittari saavuttaa tämän arvon, laukeaa tapahtuma <c>UpperLimit</c>.
170        /// </summary>
171        public ValueType MaxValue
172        {
173            get { return maxval; }
174            set { maxval = value; updateBounds(); }
175        }
176
177        #region Events
178
179        /// <summary>
180        /// Mittarin muutostapahtumankäsittelijä.
181        /// </summary>
182        public delegate void ChangeHandler( ValueType oldValue, ValueType newValue );
183
184        /// <summary>
185        /// Tapahtuu, kun mittarin arvo muuttuu.
186        /// </summary>
187        public event ChangeHandler Changed;
188
189        /// <summary>
190            /// Tapahtuu, kun mittari saavuttaa pienimmän sallitun arvonsa.
191            /// </summary>
192        public event Action LowerLimit;
193
194            /// <summary>
195            /// Tapahtuu, kun mittari saavuttaa suurimman sallitun arvonsa.
196            /// </summary>
197            public event Action UpperLimit;
198
199
200        private void OnChange( ValueType oldValue, ValueType newValue )
201        {
202            if ( Changed != null )
203                Changed( oldValue, newValue );
204        }
205
206        private void CheckLimits( ValueType oldValue, ValueType newValue )
207        {
208            if ( LowerLimit != null && newValue.CompareTo(minval) <= 0 && oldValue.CompareTo(minval) > 0 )
209            {
210                LowerLimit();
211                return;
212            }
213
214            if ( UpperLimit != null && newValue.CompareTo(maxval) >= 0 && oldValue.CompareTo(maxval) < 0 )
215            {
216                UpperLimit();
217                return;
218            }
219        }
220
221        private void CheckTriggers( ValueType oldValue, ValueType newValue )
222        {
223            if ( triggers == null ) return;
224
225            foreach ( Trigger t in triggers )
226            {
227                if ( t.direction != TriggerDirection.Down && oldValue.CompareTo( t.value ) < 0 && newValue.CompareTo( t.value ) >= 0 )
228                    t.method();
229               
230                else if ( t.direction != TriggerDirection.Up && oldValue.CompareTo( t.value ) > 0 && newValue.CompareTo( t.value ) <= 0 )
231                    t.method();
232            }
233        }
234
235        #endregion
236
237        /// <summary>
238        /// Luo uuden mittarin.
239        /// </summary>
240        /// <param name="defaultVal">Oletusarvo.</param>
241        /// <param name="minVal">Pienin sallittu arvo.</param>
242        /// <param name="maxVal">Suurin sallittu arvo.</param>
243        public Meter( ValueType defaultVal, ValueType minVal, ValueType maxVal )
244        {
245            minval = minVal;
246            maxval = maxVal;
247            defval = defaultVal;
248            val = defaultVal;
249            updateBounds();
250        }
251
252        /// <summary>
253        /// Luo uuden mittarin kopiona parametrina annetusta.
254        /// </summary>
255        /// <param name="src">Kopioitava mittari.</param>
256        public Meter( Meter<ValueType> src )
257        {
258            minval = src.minval;
259            maxval = src.maxval;
260            defval = src.defval;
261            val = src.val;
262            updateBounds();
263        }
264
265        /// <summary>
266        /// Palauttaa mittarin arvon oletusarvoonsa.
267        /// </summary>
268        public void Reset()
269        {
270            Value = DefaultValue;
271        }
272
273        /// <summary>
274        /// Asettaa mittarille arvon. Sama kuin Value-ominaisuuteen sijoitus,
275        /// mutta helpompi käyttää tapahtumakäsittelijöissä.
276        /// </summary>
277        /// <param name="value">Uusi arvo</param>
278        public void SetValue( ValueType value )
279        {
280            Value = value;
281        }
282
283        /// <summary>
284        /// Lisää mittarille rajan, jonka yli mentäessä laukaistaan aliohjelma.
285        /// </summary>
286        /// <param name="value">Mittarin arvo</param>
287        /// <param name="direction">Suunta (TriggerDirection.Irrelevant, TriggerDirection.Up tai TriggerDirection.Down)</param>
288        /// <param name="method">Aliohjelma, jota kutsutaan.</param>
289        public void AddTrigger( ValueType value, TriggerDirection direction, Action method )
290        {
291            if ( triggers == null ) triggers = new List<Trigger>();
292            triggers.Add( new Trigger( value, direction, method ) );
293        }
294
295        /// <summary>
296        /// Lisää mittarille rajan, jonka yli mentäessä laukaistaan aliohjelma.
297        /// </summary>
298        /// <param name="value">Mittarin arvo</param>
299        /// <param name="direction">Suunta (TriggerDirection.Irrelevant, TriggerDirection.Up tai TriggerDirection.Down)</param>
300        /// <param name="method">Aliohjelma, jota kutsutaan (parametrina mittarin arvo).</param>
301        public void AddTrigger( ValueType value, TriggerDirection direction, Action<ValueType> method )
302        {
303            AddTrigger( value, direction, delegate() { method( this.Value ); } );
304        }
305
306        /// <summary>
307        /// Poistaa kaikki tietylle arvolle asetetut raja-arvotapahtumat.
308        /// </summary>
309        /// <param name="value">Arvo</param>
310        public void RemoveTriggers( ValueType value )
311        {
312            if ( triggers == null ) return;
313            triggers.RemoveAll( t => t.value.Equals( value ) );
314        }
315
316        /// <summary>
317        /// Poistaa kaikki raja-arvotapahtumat, jotka kutsuvat tiettyä aliohjelmaa.
318        /// </summary>
319        /// <param name="method">Aliohjelma</param>
320        public void RemoveTriggers( Action method )
321        {
322            if ( triggers == null ) return;
323            triggers.RemoveAll( t => t.method == method );
324        }
325
326        /// <summary>
327        /// Poistaa kaikki raja-arvotapahtumat.
328        /// </summary>
329        public void ClearTriggers()
330        {
331            if ( triggers == null ) return;
332            triggers.Clear();
333        }
334
335        private static ValueType clampValue( ValueType v, ValueType min, ValueType max )
336        {
337            if (min.CompareTo(max) > 0)
338            {
339                // Maximum is less than minimum, don't clamp
340                return v;
341            }
342
343            if ( v.CompareTo( min ) < 0 )
344                return min;
345
346            if ( v.CompareTo( max ) > 0 )
347                return max;
348
349            return v;
350        }
351
352        private static void clampValue( ref ValueType v, ValueType min, ValueType max )
353        {
354            if ( min.CompareTo( max ) > 0 )
355            {
356                // Maximum is less than minimum, don't clamp
357                return;
358            }
359
360            if ( v.CompareTo( min ) < 0 )
361                v = min;
362
363            else if ( v.CompareTo( max ) > 0 )
364                v = max;
365        }
366
367        private void updateBounds()
368        {
369            clampValue( ref val, minval, maxval );
370            clampValue( ref defval, minval, maxval );
371        }
372
373        /// <summary>
374        /// Palauttaa mittarin arvon merkkijonona.
375        /// </summary>
376        public override String ToString()
377        {
378            return Value.ToString();
379        }
380    }
381
382
383    /// <summary>
384    /// Mittari, joka mittaa int-tyyppisiä arvoja.
385    /// Sidottavissa näyttöihin, kuten <c>ValueDisplay</c> ja <c>BarGauge</c>.
386    /// </summary>
387    public class IntMeter : Meter<int>
388    {
389        private List<Operation> operations = new List<Operation>();
390
391        public override double RelativeValue
392        {
393            get { return ( Value - MinValue ) / (double)( MaxValue - MinValue ); }
394            set { Value = (int)( MinValue + value * ( MaxValue - MinValue ) ); }
395        }
396
397        public IntMeter( int defaultValue )
398            : base( defaultValue, 0, int.MaxValue )
399        {
400        }
401
402        public IntMeter(int defaultValue,int minValue, int MaxValue)
403            : base(defaultValue, minValue,MaxValue)
404        {
405        }
406
407        /// <summary>
408        /// Antaa mittariolion <c>m</c> arvon, kun mittaria käytetään
409        /// sellaisessa yhteydessä, jossa vaaditaan tavallista <c>int</c>-
410        /// tyyppistä kokonaislukua.
411        /// </summary>
412        public static implicit operator int( IntMeter m )
413        {
414            return m.Value;
415        }
416
417        /// <summary>
418        /// Antaa mittariolion <c>m</c> arvon, kun mittaria käytetään
419        /// sellaisessa yhteydessä, jossa vaaditaan tavallista <c>double</c>-
420        /// tyyppistä liukulukua.
421        /// </summary>
422        public static implicit operator double( IntMeter m )
423        {
424            return (double)m.Value;
425        }
426
427        /// <summary>
428        /// Lisää jotain mittarin arvoon. Sama kuin Value-ominaisuuteen lisääminen,
429        /// mutta helpompi käyttää tapahtumakäsittelijöissä.
430        /// </summary>
431        /// <param name="change">Lisättävä luku</param>
432        public void AddValue( int change )
433        {
434            Value += change;
435        }
436
437        /// <summary>
438        /// Kertoo mittarin arvon jollakin. Sama kuin Value-ominaisuuden kertominen,
439        /// mutta helpompi käyttää tapahtumakäsittelijöissä.
440        /// </summary>
441        /// <param name="multiplier">Uusi arvo</param>
442        public void MultiplyValue( int multiplier )
443        {
444            Value *= multiplier;
445        }
446
447        /// <summary>
448        /// Kertoo mittarin arvon jollakin. Sama kuin Value-ominaisuuden kertominen,
449        /// mutta helpompi käyttää tapahtumakäsittelijöissä.
450        /// </summary>
451        /// <param name="multiplier">Uusi arvo</param>
452        public void MultiplyValue( double multiplier )
453        {
454            Value = (int)Math.Round( Value * multiplier );
455        }
456
457        /// <summary>
458        /// Lisää tietyn summan mittariin tasaisesti tietyn ajan sisällä.
459        /// </summary>
460        /// <param name="change">Kuinka paljon lisätään</param>
461        /// <param name="seconds">Aika joka lisämiseen kuluu</param>
462        /// <param name="onComplete">Aliohjelma, joka suoritetaan kun lisäys on valmis.</param>
463        /// <returns>Operation-tyyppinen muuttuja, jolla voi hallita tapahtuvaa muutosta</returns>
464        public Operation AddOverTime( int change, double seconds, Action onComplete )
465        {
466            Operation op = AddOverTime( change, seconds );
467            op.Finished += onComplete;
468            return op;
469        }
470
471        /// <summary>
472        /// Lisää tietyn summan mittariin tasaisesti tietyn ajan sisällä.
473        /// </summary>
474        /// <param name="change">Kuinka paljon lisätään</param>
475        /// <param name="seconds">Aika joka lisämiseen kuluu</param>
476        /// <returns>Operation-tyyppinen muuttuja, jolla voi hallita tapahtuvaa muutosta</returns>
477        public Operation AddOverTime( int change, double seconds )
478        {
479            Operation op = new IntMeterAddOperation( this, change, seconds );
480            op.Finished += delegate { operations.Remove( op ); };
481            op.Stopped += delegate { operations.Remove( op ); };
482            operations.Add( op );
483            return op;
484        }
485
486        /// <summary>
487        /// Pysäyttää AddOverTime-metodilla tehtävät lisäykset mittariin.
488        /// </summary>
489        public void Stop()
490        {
491            while ( operations.Count > 0 )
492                operations[0].Stop();
493        }
494
495        internal override double GetValue()
496        {
497            return Value;
498        }
499
500        internal override double GetMinValue()
501        {
502            return MinValue;
503        }
504
505        internal override double GetMaxValue()
506        {
507            return MaxValue;
508        }
509    }
510
511    /// <summary>
512    /// Mittari, joka mittaa double-tyyppisiä arvoja.
513    /// Sidottavissa näyttöihin, kuten <c>ValueDisplay</c> ja <c>BarGauge</c>.
514    /// </summary>
515    public class DoubleMeter : Meter<double>
516    {
517        private List<Operation> operations = new List<Operation>();
518
519        public override double RelativeValue
520        {
521            get { return ( Value - MinValue ) / ( MaxValue - MinValue ); }
522            set { Value = MinValue + value * ( MaxValue - MinValue ); }
523        }
524
525        public DoubleMeter( double defaultValue )
526            : base( defaultValue, 0.0, double.MaxValue )
527        {
528        }
529
530        public DoubleMeter(double defaultValue,double minValue, double maxValue)
531            : base(defaultValue,minValue, maxValue)
532        {
533        }
534
535        /// <summary>
536        /// Antaa mittariolion <c>m</c> arvon, kun mittaria käytetään
537        /// sellaisessa yhteydessä, jossa vaaditaan tavallista <c>double</c>-
538        /// tyyppistä liukulukua.
539        /// </summary>
540        public static implicit operator double( DoubleMeter m )
541        {
542            return m.Value;
543        }
544
545        /// <summary>
546        /// Lisää jotain mittarin arvoon. Sama kuin Value-ominaisuuteen lisääminen,
547        /// mutta helpompi käyttää tapahtumakäsittelijöissä.
548        /// </summary>
549        /// <param name="change">Lisättävä luku</param>
550        public void AddValue( double change )
551        {
552            Value += change;
553        }
554
555        /// <summary>
556        /// Kertoo mittarin arvon jollakin. Sama kuin Value-ominaisuuden kertominen,
557        /// mutta helpompi käyttää tapahtumakäsittelijöissä.
558        /// </summary>
559        /// <param name="multiplier">Uusi arvo</param>
560        public void MultiplyValue( double multiplier )
561        {
562            Value *= multiplier;
563        }
564
565        /// <summary>
566        /// Lisää tietyn summan mittariin tasaisesti tietyn ajan sisällä.
567        /// </summary>
568        /// <param name="change">Kuinka paljon lisätään</param>
569        /// <param name="seconds">Aika joka lisämiseen kuluu</param>
570        /// <param name="onComplete">Aliohjelma, joka suoritetaan kun lisäys on valmis.</param>
571        /// <returns>Operation-tyyppinen muuttuja, jolla voi hallita tapahtuvaa muutosta</returns>
572        public Operation AddOverTime( double change, double seconds, Action onComplete )
573        {
574            Operation op = AddOverTime( change, seconds );
575            op.Finished += onComplete;
576            return op;
577        }
578
579        /// <summary>
580        /// Lisää tietyn summan mittariin tasaisesti tietyn ajan sisällä.
581        /// </summary>
582        /// <param name="change">Kuinka paljon lisätään</param>
583        /// <param name="seconds">Aika joka lisämiseen kuluu</param>
584        /// <returns>Operation-tyyppinen muuttuja, jolla voi hallita tapahtuvaa muutosta</returns>
585        public Operation AddOverTime( double change, double seconds )
586        {
587            Operation op = new DoubleMeterAddOperation( this, change, seconds );
588            op.Finished += delegate { operations.Remove( op ); };
589            op.Stopped += delegate { operations.Remove( op ); };
590            operations.Add( op );
591            return op;
592        }
593
594        /// <summary>
595        /// Pysäyttää AddOverTime-metodilla tehtävät lisäykset mittariin.
596        /// </summary>
597        public void Stop()
598        {
599            while ( operations.Count > 0 )
600                operations[0].Stop();
601        }
602
603        internal override double GetValue()
604        {
605            return Value;
606        }
607
608        internal override double GetMinValue()
609        {
610            return MinValue;
611        }
612
613        internal override double GetMaxValue()
614        {
615            return MaxValue;
616        }
617    }
618}
Note: See TracBrowser for help on using the repository browser.