1607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok/*
2607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok * Copyright (C) 2012 The Android Open Source Project
3607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok *
4607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok * Licensed under the Apache License, Version 2.0 (the "License");
5607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok * you may not use this file except in compliance with the License.
6607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok * You may obtain a copy of the License at
7607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok *
8607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok *      http://www.apache.org/licenses/LICENSE-2.0
9607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok *
10607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok * Unless required by applicable law or agreed to in writing, software
11607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok * distributed under the License is distributed on an "AS IS" BASIS,
12607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok * See the License for the specific language governing permissions and
14607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok * limitations under the License.
15607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok */
16607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
17607a9244861ee22c25aaea6ffdfa19fccf497b0bsatokpackage com.android.inputmethod.latin;
18607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
19fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satokimport android.text.format.DateUtils;
20fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satokimport android.util.Log;
21fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
221e11c44d1b5f9ddf593c5407cb14c458be0056f2Tadashi G. Takaokapublic final class UserHistoryForgettingCurveUtils {
23fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok    private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
24fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok    private static final boolean DEBUG = false;
25607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    private static final int FC_FREQ_MAX = 127;
26607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    /* package */ static final int COUNT_MAX = 3;
27607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    private static final int FC_LEVEL_MAX = 3;
28607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    /* package */ static final int ELAPSED_TIME_MAX = 15;
29607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
30fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok    private static final long ELAPSED_TIME_INTERVAL_MILLIS = ELAPSED_TIME_INTERVAL_HOURS
31fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            * DateUtils.HOUR_IN_MILLIS;
32607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    private static final int HALF_LIFE_HOURS = 48;
33ec2981a487b91a682caade486700d8b2377a5c52satok    private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1);
34607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
35fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok    private UserHistoryForgettingCurveUtils() {
36fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        // This utility class is not publicly instantiable.
37fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok    }
38fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
39a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka    public static final class ForgettingCurveParams {
40fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        private byte mFc;
41fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        long mLastTouchedTime = 0;
42c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka        private final boolean mIsValid;
43fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
44fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        private void updateLastTouchedTime() {
45fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            mLastTouchedTime = System.currentTimeMillis();
46fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        }
47fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
48c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka        public ForgettingCurveParams(boolean isValid) {
49c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka            this(System.currentTimeMillis(), isValid);
50fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        }
51fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
52c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka        private ForgettingCurveParams(long now, boolean isValid) {
537214617622fce8f3fea6620e782c16336260a2a3Jean Chalard            this(pushCount((byte)0, isValid), now, now, isValid);
54fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        }
55fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
56c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka        /** This constructor is called when the user history bigram dictionary is being restored. */
57fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        public ForgettingCurveParams(int fc, long now, long last) {
58c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka            // All words with level >= 1 had been saved.
59c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka            // Invalid words with level == 0 had been saved.
60c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka            // Valid words words with level == 0 had *not* been saved.
61c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka            this(fc, now, last, fcToLevel((byte)fc) > 0);
62c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka        }
63c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka
64c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka        private ForgettingCurveParams(int fc, long now, long last, boolean isValid) {
65c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka            mIsValid = isValid;
66fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            mFc = (byte)fc;
67fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            mLastTouchedTime = last;
68fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            updateElapsedTime(now);
69fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        }
70fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
71c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka        public boolean isValid() {
72c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka            return mIsValid;
73c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka        }
74c88f61215c5b9ca6e0cc3f776e3b7da19eec9caeSatoshi Kataoka
75fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        public byte getFc() {
76fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            updateElapsedTime(System.currentTimeMillis());
77fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            return mFc;
78fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        }
79fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
80fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        public int getFrequency() {
81fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            updateElapsedTime(System.currentTimeMillis());
82fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
83fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        }
84fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
85fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        public int notifyTypedAgainAndGetFrequency() {
86fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            updateLastTouchedTime();
87fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            // TODO: Check whether this word is valid or not
88fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            mFc = pushCount(mFc, false);
89fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
90fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        }
91fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
92fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        private void updateElapsedTime(long now) {
93fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            final int elapsedTimeCount =
94fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok                    (int)((now - mLastTouchedTime) / ELAPSED_TIME_INTERVAL_MILLIS);
95fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            if (elapsedTimeCount <= 0) {
96fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok                return;
97fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            }
98ec2981a487b91a682caade486700d8b2377a5c52satok            if (elapsedTimeCount >= MAX_PUSH_ELAPSED) {
99ec2981a487b91a682caade486700d8b2377a5c52satok                mLastTouchedTime = now;
100ec2981a487b91a682caade486700d8b2377a5c52satok                mFc = 0;
101ec2981a487b91a682caade486700d8b2377a5c52satok                return;
102ec2981a487b91a682caade486700d8b2377a5c52satok            }
103fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            for (int i = 0; i < elapsedTimeCount; ++i) {
104fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok                mLastTouchedTime += ELAPSED_TIME_INTERVAL_MILLIS;
105fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok                mFc = pushElapsedTime(mFc);
106fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            }
107fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        }
108fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok    }
109fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
110607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    /* package */ static  int fcToElapsedTime(byte fc) {
111607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        return fc & 0x0F;
112607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    }
113607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
114607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    /* package */ static int fcToCount(byte fc) {
115607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        return (fc >> 4) & 0x03;
116607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    }
117607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
118607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    /* package */ static int fcToLevel(byte fc) {
119607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        return (fc >> 6) & 0x03;
120607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    }
121607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
122607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    private static int calcFreq(int elapsedTime, int count, int level) {
123607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        if (level <= 0) {
124fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            // Reserved words, just return -1
125fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            return -1;
126607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        }
127607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        if (count == COUNT_MAX) {
128607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            // Temporary promote because it's frequently typed recently
129607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            ++level;
130607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        }
131607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
132607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
133607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        return MathUtils.SCORE_TABLE[l - 1][et];
134607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    }
135607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
136607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    /* pakcage */ static byte calcFc(int elapsedTime, int count, int level) {
137607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
138607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        final int c = Math.min(COUNT_MAX, Math.max(0, count));
139607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
140607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        return (byte)(et | (c << 4) | (l << 6));
141607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    }
142607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
143607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    public static int fcToFreq(byte fc) {
144607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        final int elapsedTime = fcToElapsedTime(fc);
145607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        final int count = fcToCount(fc);
146607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        final int level = fcToLevel(fc);
147607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        return calcFreq(elapsedTime, count, level);
148607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    }
149607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
150607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    public static byte pushElapsedTime(byte fc) {
151607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        int elapsedTime = fcToElapsedTime(fc);
152607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        int count = fcToCount(fc);
153607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        int level = fcToLevel(fc);
154607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        if (elapsedTime >= ELAPSED_TIME_MAX) {
155607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            // Downgrade level
156607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            elapsedTime = 0;
157607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            count = COUNT_MAX;
158607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            --level;
159607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        } else {
160607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            ++elapsedTime;
161607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        }
162607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        return calcFc(elapsedTime, count, level);
163607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    }
164607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
165607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    public static byte pushCount(byte fc, boolean isValid) {
166607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        final int elapsedTime = fcToElapsedTime(fc);
167607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        int count = fcToCount(fc);
168607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        int level = fcToLevel(fc);
169607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        if ((elapsedTime == 0 && count >= COUNT_MAX) || (isValid && level == 0)) {
170607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            // Upgrade level
171607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            ++level;
172607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            count = 0;
173fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            if (DEBUG) {
174fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok                Log.d(TAG, "Upgrade level.");
175fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok            }
176607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        } else {
177607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            ++count;
178607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        }
179607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        return calcFc(0, count, level);
180607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    }
181607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok
182fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok    // TODO: isValid should be false for a word whose frequency is 0,
183fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok    // or that is not in the dictionary.
184bc5688506229bd5cd5e6f4dcdc73c21dc6b80ecbSatoshi Kataoka    /**
185bc5688506229bd5cd5e6f4dcdc73c21dc6b80ecbSatoshi Kataoka     * Check wheather we should save the bigram to the SQL DB or not
186bc5688506229bd5cd5e6f4dcdc73c21dc6b80ecbSatoshi Kataoka     */
187bc5688506229bd5cd5e6f4dcdc73c21dc6b80ecbSatoshi Kataoka    public static boolean needsToSave(byte fc, boolean isValid, boolean addLevel0Bigram) {
188fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        int level = fcToLevel(fc);
189bc5688506229bd5cd5e6f4dcdc73c21dc6b80ecbSatoshi Kataoka        if (level == 0) {
190bc5688506229bd5cd5e6f4dcdc73c21dc6b80ecbSatoshi Kataoka            if (isValid || !addLevel0Bigram) {
191bc5688506229bd5cd5e6f4dcdc73c21dc6b80ecbSatoshi Kataoka                return false;
192bc5688506229bd5cd5e6f4dcdc73c21dc6b80ecbSatoshi Kataoka            }
193fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        }
194fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        final int elapsedTime = fcToElapsedTime(fc);
195fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok        return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0);
196fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok    }
197fd53b8cc2b78acd7e33f4dc39cfc2faaea92f0f8satok
198a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka    private static final class MathUtils {
199607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1];
200607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        static {
201607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            for (int i = 0; i < FC_LEVEL_MAX; ++i) {
202d10c473347c7e21c383c56786c9eb96fd6513a5cJean Chalard                final float initialFreq;
203607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                if (i >= 2) {
2047214617622fce8f3fea6620e782c16336260a2a3Jean Chalard                    initialFreq = FC_FREQ_MAX;
205607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                } else if (i == 1) {
206d10c473347c7e21c383c56786c9eb96fd6513a5cJean Chalard                    initialFreq = FC_FREQ_MAX / 2;
207607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                } else if (i == 0) {
208d10c473347c7e21c383c56786c9eb96fd6513a5cJean Chalard                    initialFreq = FC_FREQ_MAX / 4;
209607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                } else {
210607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                    continue;
211607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                }
212607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
213d10c473347c7e21c383c56786c9eb96fd6513a5cJean Chalard                    final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
214e7b34b9f867b64eabc3606e5ef21e26eda8de0f6Ken Wakasa                    final float freq = initialFreq
215bcec82de66f52655593dc233346f11468f5077a0Ken Wakasa                            * (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS);
216607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                    final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
217607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                    SCORE_TABLE[i][j] = intFreq;
218607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok                }
219607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok            }
220607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok        }
221607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok    }
222607a9244861ee22c25aaea6ffdfa19fccf497b0bsatok}
223