double evaluateFingerObstacleCost(FingerState state, float minPreparation, NoteChord obsNote, float startPosition, int startHeight, int pitch)
            {
                float  deltaX   = startPosition - Piano.KeyPositions[pitch];
                float  deltaY   = startHeight - Piano.getKeyHeight(pitch);
                double distance = Math.Min(Math.Sqrt(deltaX * deltaX + deltaY * deltaY), 5) + 1;

                float prepareTime = startTime - minPreparation;

                float importance = NotationUtils.getNoteImportanceInChord(obsNote, state.Pitch);

                debug += string.Format("O{0},{1},{2},{3};", state.Pitch, prepareTime, state.Release, state.Press);

                if (prepareTime >= state.Release)
                {
                    double coeff = CostCoeff.FINGER_MOVE_SPEED_PUNISH * Math.Pow(0.1, (startTime - state.Release) / minPreparation);
                    return(coeff * distance);
                }
                else if (prepareTime >= state.Press)
                {
                    double speedCost  = CostCoeff.FINGER_MOVE_SPEED_PUNISH * distance;
                    double cutoffCost = CostCoeff.NOTE_CUTOFF_PUNISH * importance * (state.Release - prepareTime) / (state.Release - state.Press);

                    return(speedCost + cutoffCost);
                }
                else
                {
                    return(CostCoeff.OMIT_KEY_PUNISH * importance);
                }
            }
        void tryFingerChoice(int[] pitches, ref List <FingerChord> choices, FingerChord constrait, FingerChord fc, int currentNoteIndex, int emptyQuota)
        {
            if (currentNoteIndex >= pitches.Length)
            {
                choices.Add(new FingerChord(fc));

                /*Finger[] fa = new Finger[fc.Values.Count];
                 * fc.Values.CopyTo(fa, 0);
                 * UnityEngine.Debug.Log("fc: " + String.Join(",", Array.ConvertAll(fa, x => ((int)x).ToString())));*/
            }
            else
            {
                int currentPitch = pitches[currentNoteIndex];

                if (constrait != null && constrait.ContainsKey(currentPitch) && constrait[currentPitch] != Finger.EMPTY)
                {
                    fc[currentPitch] = constrait[currentPitch];
                    tryFingerChoice(pitches, ref choices, constrait, fc, currentNoteIndex + 1, emptyQuota);
                }
                else
                {
                    foreach (Finger f in FingerConstants.SolveTypeFingers[HandType])
                    {
                        bool pass = true;
                        for (int index = currentNoteIndex - 1; index >= 0; --index)
                        {
                            int    pitch  = pitches[index];
                            Finger finger = fc[pitch];

                            float distance = Piano.pitchPairDistance(pitch, currentPitch);

                            pass = FingerConstants.testFingerDistance(finger, f, Config, distance);
                            if (!pass)
                            {
                                break;
                            }
                        }

                        if (pass)
                        {
                            fc[currentPitch] = f;
                            tryFingerChoice(pitches, ref choices, constrait, fc, currentNoteIndex + 1, emptyQuota);
                        }
                    }

                    if (emptyQuota > 0)
                    {
                        fc[currentPitch] = Finger.EMPTY;
                        tryFingerChoice(pitches, ref choices, constrait, fc, currentNoteIndex + 1, emptyQuota - 1);
                    }
                }
            }
        }
        Choice evaluateChordChoice(FingerChord chord, int index)
        {
            float deltaTime = index > 0 ? NoteSeq[index].start - NoteSeq[index - 1].start : 0;

            NoteChord note = NoteSeq[index];

            HandConfig.RangePair wrists = Config.getFingerChordWristRange(chord);

            double cost = 0;

            // wrist position naturality reward
            if (wrists.left != null)
            {
                float distance = Math.Abs(wrists.left.middle - -HandConfig.WristNaturePosition);
                cost = Math.Pow(distance / 14, 4) * CostCoeff.WRIST_POSITION_NATURALITY_REWARD;
            }

            if (wrists.right != null)
            {
                float distance = Math.Abs(wrists.right.middle - HandConfig.WristNaturePosition);
                cost = Math.Pow(distance / 14, 4) * CostCoeff.WRIST_POSITION_NATURALITY_REWARD;
            }

            // wrist crowd punish
            if (wrists.left != null && wrists.right != null)
            {
                float distance = Math.Max(Math.Abs(wrists.left.high - wrists.right.low), Math.Abs(wrists.right.high - wrists.left.low));
                if (distance < 5)
                {
                    cost += CostCoeff.WRIST_CROWD_PUNISH * (5f - distance) / 5f;
                }
            }

            foreach (Finger f in chord.Values)
            {
                // shift fingers punish
                if (Math.Abs((int)f) > 10)
                {
                    cost += CostCoeff.SHIFT_FINGERS_PUNISH;
                }
            }

            int leftFingerCount  = 0;
            int rightFingerCount = 0;

            foreach (var pair in chord)
            {
                if (pair.Value != Finger.EMPTY)
                {
                    // black key short punish
                    if (Piano.isBlackKey(pair.Key))
                    {
                        int finger = Math.Abs((int)pair.Value);
                        int first  = (int)Math.Floor(finger / 10f) - 1;
                        int second = finger % 10 - 1;

                        float sh = HandConfig.BlackKeyShort[second];
                        if (first >= 0)
                        {
                            sh = Math.Max(HandConfig.BlackKeyShort[first], sh);
                        }

                        cost += sh * CostCoeff.BLACK_KEY_SHORT_PUNISH;
                    }

                    if (pair.Value > Finger.EMPTY)
                    {
                        ++rightFingerCount;
                    }
                    else if (pair.Value < Finger.EMPTY)
                    {
                        ++leftFingerCount;
                    }
                }
                else
                {
                    // omit key punish
                    float importance = NotationUtils.getNoteImportanceInChord(note, pair.Key);
                    cost += CostCoeff.OMIT_KEY_PUNISH * importance;
                }
            }

            // multiple fingers punish
            if (leftFingerCount > 0)
            {
                float value = leftFingerCount / 5f;
                cost += CostCoeff.MULTIPLE_FINGERS_PUNISH * value * value;
            }
            if (rightFingerCount > 0)
            {
                float value = rightFingerCount / 5f;
                cost += CostCoeff.MULTIPLE_FINGERS_PUNISH * value * value;
            }

            return(new Choice {
                chord = chord, staticCost = cost, wrists = wrists, deltaTime = deltaTime, node = new TreeNodeChoice()
            });
        }
            double evaluateSingleArmCost(HandConfig.Range lastWrist, HandConfig.Range currentWrist, FingerState[] lastFs, FingerState[] currentFs, int hand)
            {
                double cost = 0;

                // wrist offset punish

                //		by middle
                cost  += Math.Abs(currentWrist.middle - lastWrist.middle) * CostCoeff.WRIST_OFFSET_MIDDLE_PUNISH / timeUnit;
                debug += string.Format("FM: {0}\\n", Math.Abs(currentWrist.middle - lastWrist.middle) / timeUnit);

                //		by range
                if (!(lastWrist.low < currentWrist.high && lastWrist.high > currentWrist.low))
                {
                    cost  += Math.Min(Math.Abs(currentWrist.low - lastWrist.high), Math.Abs(lastWrist.low - currentWrist.high)) * CostCoeff.WRIST_OFFSET_RANGE_PUNISH / timeUnit;
                    debug += string.Format("FR: {0}\\n", Math.Min(Math.Abs(currentWrist.low - lastWrist.high), Math.Abs(lastWrist.low - currentWrist.high)) / timeUnit);
                }

                // finger speed punish
                if (lastFs != null)
                {
                    List <int> fingers = new List <int>();
                    List <int> pitches = new List <int>();

                    foreach (var pair in fingerChord)
                    {
                        int finger = (int)pair.Value * hand;
                        if (finger > 0)
                        {
                            int first  = (int)Math.Floor(finger / 10f) - 1;
                            int second = finger % 10 - 1;

                            if (first > 0)
                            {
                                fingers.Add(first);
                                pitches.Add(pair.Key);
                            }
                            fingers.Add(second);
                            pitches.Add(pair.Key);
                        }
                    }

                    for (int i = 0; i < fingers.Count; ++i)
                    {
                        int finger = fingers[i];
                        int pitch  = pitches[i];
                        //float position = Piano.KeyPositions[pitch];

                        FingerState state = lastFs[finger];

                        float minimumPreparationTime = s_BenchmarkDuration * 2 * HandConfig.MinimumPreparationRate;

                        // self obstacle
                        if (state.Index >= 0)
                        {
                            NoteChord obsNote = s_NoteSeq[state.Index];
                            cost += evaluateFingerObstacleCost(state, minimumPreparationTime * 2, obsNote, state.Position, state.Height, pitch);
                        }

                        // other obstacles
                        if (finger > 0)
                        {
                            for (int of = 1; of < 5; ++of)
                            {
                                // ignore self finger
                                if (of == finger)
                                {
                                    continue;
                                }

                                FingerState obsState = lastFs[of];
                                if (obsState.Index >= 0)
                                {
                                    int height = Piano.getKeyHeight(pitch);

                                    // allow ring finger on black cross pinky finger on white
                                    if (finger == 3 && of == 4 && height > obsState.Height)
                                    {
                                        continue;
                                    }

                                    float startPosition = obsState.Position + (finger - of) * hand;
                                    int   startHeight   = obsState.Height;

                                    float targetPosition = currentFs[finger].Position;

                                    if (state.Index >= 0)
                                    {
                                        startHeight = state.Height;

                                        if (finger * hand < of * hand)
                                        {
                                            startPosition = Math.Min(startPosition, state.Position);
                                        }
                                        else
                                        {
                                            startPosition = Math.Max(startPosition, state.Position);
                                        }
                                    }

                                    //debug += string.Format("of: {0}, {1}, {2}, {3}\\n", of, startPosition, obsState.Position, targetPosition);

                                    if ((targetPosition - obsState.Position) * (obsState.Position - startPosition) <= 0)
                                    {
                                        continue;
                                    }

                                    NoteChord obsNote = s_NoteSeq[obsState.Index];
                                    cost += evaluateFingerObstacleCost(obsState, minimumPreparationTime, obsNote, startPosition, startHeight, pitch);

                                    //debug += "O" + ((of + 1) * hand).ToString() + ","/* + (startTime - minimumPreparationTime - obsState.Release).ToString() + ";"*/;
                                }
                            }
                        }
                    }
                }

                // TODO:

                return(cost);
            }
            static FingerState[] generateFingerStates(FingerState[] parentStates, int hand, FingerChord chord, NoteChord nc, int index)
            {
                FingerState[] states = null;

                if (parentStates != null)
                {
                    states = new FingerState[parentStates.Length];
                    Array.Copy(parentStates, states, states.Length);
                }

                foreach (var pair in chord)
                {
                    int finger = (int)pair.Value * hand;
                    if (finger > 0)
                    {
                        if (states == null)
                        {
                            states = Enumerable.Repeat(new FingerState {
                                Press = -10000f, Release = -10000f, Index = -1, Height = 0
                            }, 5).ToArray();
                            for (int i = 0; i < states.Length; ++i)
                            {
                                states[i].Position = (HandConfig.WristNaturePosition + i - 2) * hand;
                            }
                        }

                        Note note = nc.notes[pair.Key];
                        UnityEngine.Debug.Assert(note != null, "note and finger chord mismatch");

                        float position = Piano.KeyPositions[pair.Key];
                        int   height   = Piano.getKeyHeight(pair.Key);

                        int first  = (int)Math.Floor(finger / 10f) - 1;
                        int second = finger % 10 - 1;

                        if (first >= 0)
                        {
                            states[first].Press    = note.start;
                            states[first].Release  = note.start;
                            states[first].Pitch    = pair.Key;
                            states[first].Position = position;
                            states[first].Height   = height;
                            states[first].Index    = index;
                        }

                        UnityEngine.Debug.Assert(second >= 0 && second < 5, "invalid finger value");
                        {
                            states[second].Press    = note.start;
                            states[second].Release  = note.start + note.duration;
                            states[second].Pitch    = pair.Key;
                            states[second].Position = position;
                            states[second].Height   = height;
                            states[second].Index    = index;

                            //fixFingerStatesObstacle(ref states, hand, second);
                        }
                    }
                }

                return(states);
            }