none
להפסיק ריצה של פונקציה עד שנתפס event כלשהו RRS feed

  • שאלה

  • שלום לכולם,

    אתחיל עם הקדמה לפני השאלה :)

    בניתי משחק XO, וניסיתי לכתוב את הקוד כך שהלוגיקה תעמוד בפני עצמה, וכל אחד אחר יוכל לבוא ולממש את הממשק בעצמו.
    בהתחלה בניתי ממשק Console והמשחק פועל כמו שצריך, אך כשניסיתי לבנות ממשק Windows forms נתקלתי בבעיה.

    המשחק עובד כך שכאשר מגיע תורו של אחד השחקנים הוא זורק event והפונקציה שמטפלת  ב- event צריכה להחזיר int (מס' התא שהשחקן בחר לסמן).
    קראתי לאותה פונקציה GetCellNumber.

    ב- Console לא הייתה לי בעיה. הדפסתי הודעה למשתמש "עכשיו תורך לשחק. הכנס את מספר התא הרצוי: " והפונקציה Console.ReadLine המתינה עד אשר השחקן לחץ על המקש ENTER ורק לאחר מכן המשיכה לשורות הקוד הבאות (שורותו הקוד הבאות = להחזיר את הערך שהתעדכן מהפונקציה Console.ReadLine).

    ב- WinForms רציתי לממש את זה כך שברגע שהפונקציה GetCellNumber מופעלת, התכנית תחכה לאירוע של Click מאחד הכפתורים (שיעדכן את מס' התא שצריך להיות מוחזר) ואז לאחר עדכון מס' התא (ע"י ה- Click) אבצע return לערך בפונקציה GetCellNumber.

    קראתי קצת על המחלקה AutoResetEvent ועל הפונקציה WaitOne. חשבתי לשים ב- GetCellNumber את הפונקציה WaitOne, ואז כאשר מופעלת הפונקציה של ה- Click היא תבטל את ההמתנה (לאחר עדכון ערכים), והפונקציה GetCellNumber תמשיך לרוץ (השורה הבאה בקוד מחזירה את הערך שעודכן ע"י ה- Click).

    הבעיה שהפונקציה WaitOne גורמת לחלון של התכנית לקפוא, וכמובן שאי אפשר ללחוץ על שום כפתור.

    אשמח אם מישהו יוכל לתת לי כיוון בנושא.
    האם יש אפשרות טובה יותר להתמודד עם המצב ? אולי היה נכון יותר לתכנן את המשחק כך שיעבוד בצורה אחרת ?

    מחכה לעזרתכם,
    תודה!
    • נערך על-ידי eli789 יום ראשון 08 יוני 2014 10:53
    יום ראשון 08 יוני 2014 10:50

תשובות

  • שלום אלי

    1. במום סיפורים אם תצרף את הקוד והפרוייקט אנשים יוכלו לבדוק ולראות את הדברים. אני ממליץ לצרף קוד רלווטי בתוכן ההודעה וקובץ פרויקט להורדה בקישור.

    2. אני לא מבין מה הקשר לאירועים ולמה עשית שימוש באירוע על מנת להודיע שהגיע תורו של שחקן מסוים. אחרי מה אתה עוקב ומנטר שאתה רוצה שהאירוע שהאירוע יבוצע? האם יכול להיות שאתה עושה שימוש לא מדוייק במילה השמורה EVENT ?

    שוב פעם הקוד יוכל להבהיר את הדברים...

    3. "התכנית תחכה לאירוע של Click מאחד הכפתורים "

    כאן מגיע בדיוק השימוש באירועים :-) 
    בדוק אם הקישורים הבאים עוזרים לך: 
    http://www.dotnetperls.com/button או את הקישור הבא למשל http://msdn.microsoft.com/en-us/library/system.windows.forms.control.onclick(v=vs.110).aspx

    אתה יכול להוסיף אירוע לכל הכפתורים בתכנית ולקבוע שהאירוע יגרור הפעלה של מתודה כלשהי

    * אנא צרף קודים רלוונטיים ואת הפרויקט כדי להבהיר מה עשית 


    [Personal Site] [Blog] [Facebook]signature

    • סומן כתשובה על-ידי eli789 יום שלישי 10 יוני 2014 07:29
    יום ראשון 08 יוני 2014 12:54
    מנחה דיון
  • טוב הורדתי את הפרויקט, ואם יהיה עדיין צורך אז אני אנסה לעבור עליו בסוף השבע, בינתיים הצצתי מהר ברפרוף על הקוד, אז אני אצרף כמה נקודות שיכולות לעזור באופן כללי.

    * אני מציין שוב שלא עברתי על הקוד כולו ואפילו לא על חלקו לעומק אלא רק בקריאה ראשונה.
    לכן נקודות אלו יכולות להיות לא מתאימות בדיוק למקרה שלך

    לפני שאני מתחיל עם הנקודות אני אתייחס בקצרה לנקודות שאתה מעלה בהודעה האחרונה:

    1. "כאשר ה- event הזה נזרק, ותפסתי אותו בפרוייקט מסוג Console, ביצעתי קליטה של מספר התא הרצוי בעזרת הפונקציה Console.ReadLine. הפונקציה המתינה עד אשר המשתמש יקיש על ENTER ורק אז המשיכה."

    יש הבדל מהותי בין אפליקציית CONSOLE לבין אפליקציית WINDOWS FORMS והוא ה INTERFACE המאפשר קלט/פלט. ב CONSOLE אנחנו מכניסים קלט בעזרת ה SHELL, העכבר למשל לא מהווה בברירת המחדל מקור לקלט, אין GUI שמאפשר לחיצה, שחרור, פוקוס, וכו' בצור דמה לאפליקציה חלונאית. כל האירועים של חלונות אינם רלוונטיים כמובן. לכן באפליקציית CONSOLE כל האפליקציה המתינה לקלט היחיד שהיא מאזינה לו אבל באפליקציה חלונאית יש קלט שמתקבל בצורה שונה.

    2. "הבעיה: איזה ערך אחזיר ? הערך מתעדכן כאשר מתבצעת לחיצה על כפתור כלשהו. איך אני ממתין עד שהמשתמש ילחץ על כפתור כלשהו כמו שהפונקציה Console.ReadLine ממתינה עד שהמשתמש ילחץ על המקש ENTER ?"

    * זו בדיוק הלוגיקה שאמרתי בהודעה קודמת שלי שאני חושב שהיא לא מתאימה. כל החלק של השימוש באירוע (יותר מדויק דרך אגב זה שאתה עובד עם delegate ולא אירוע אם שמתי לב טוב, כל אירוע מבוסס על delegate אבל לא כל שימוש ב delegate הוא אירוע וההבדל גדול מאוד, אבל לא קשור לשאלות שלך כרגע) וכל השימוש ב GetCellNumber הוא פעולה מיותרת לחלוטין לדעתי מכיוון שההתנהלות צריכה להיות הפוכה. לא הטופס צריך לקבל אירוע אלא הטופס צריך לשלוח את האירוע של הלחיצה על הכפתור.

    בכל מקרה אם אתה מחפש פתרון תאורטית לבעיה שאתה מציג אז אתה יכול למשל בנקודה זו להוסיף לכפתורים אירועים של לחיצה ולהמתין לאירוע על מנת להמשיך לבצע פעולות. האירוע לחיצה ימשיך ריצה של קוד אחר שהוא המשך המשחק. בכל מקרה צריך לתפוס את האירוע של הלחיצה על הכפתור כדי להמשיך את הריצה של המשחק בצורה זו או אחרת. 
    * אם היית עובד עם מספר THREAD-ים אז היית יכול לעצור את ה THREAD אבל אם שמתי לב טוב אתה עובד עם ה THRED הראשי בלבד ולכן אין משמעות לעצירה שלו.

    למעשה  אתה לא צריך לבצע כלום, בניגוד לאפליקציית CONSOLE שאם לא מכניסים פעולה אז היא מגיעה לסיום ונסגרת הרי שאפליקציה חלונאית ממשיכה להחזיק את הטופס פתוח והוא יכול לשלוח אירועים כל הזמן. כל הלוגיקה שונה מבחינה זו בין סוגי האפליקציות הנ"ל. פשוט תשאיר את המצב ותמתין לאירוע הלחיצה על הכפתור שיקרא לביצע הפעולה הבאה

    3. AutoResetEvent לא נועד להקפיא חלון אלא את ה THREAD. בקצרה: אין שום צורך בשימוש במחלקה זו לפי מה שאני מבין את הצרכים שלך בכלל. כל החלק הזה צריך לרדת (למרות שאולי תאורטית אפשר להלביש משהו בכוח שיעשה שימוש בכך, משל הפעלת THREAD נוסף בו יבוצע פעולה שתמתין לאירוע... או אפילו שימוש ב THREAD הראשי).

    מדובר במחלקה המייצגת אובייקט המטפל באירוע המתנה של הליכון (כן זה התרגום של THREAD, הליכון מהמילה הליך). אובייקט יכול להיות במצב signaled state או לא. אובייקט במצב signaled לא יגרום להליכון שממתין לאובייקט להיחסם. אובייקט שלא במצב signaled יגרום לכל הליכון שממתין לאובייקט להיחסם עד לרגע שהאובייקט עובר למצב signaled. מטפל אירוע המתנה מודיע על הליכון שנכנס למצב המתנה 

    /// Set: Sets the state of the event to signaled, allowing one or more waiting threads to proceed. (Inherited from EventWaitHandle.)
    /// WaitOne(): Blocks the current thread until the current WaitHandle receives a signal. (Inherited from WaitHandle.)
     /// In XNA Framework, this member is overridden by WaitOne().

    תעבור על הקישורים הבאים ותריץ את הקודים כדי להבין יותר (זה נושא לא פשוט להבנה ראשונה, אבל אחרי שמסתדרים הדברים בראש הוא יעיל מאוד לפעמים):

    http://stackoverflow.com/questions/17613342/signalled-and-non-signalled-state-of-event
    http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.autoresetevent.aspx
    http://msdn.microsoft.com/en-us/library/system.threading.waithandle.aspx
    http://msdn.microsoft.com/en-us/library/system.threading.eventwaithandle.aspx
    http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.aspx



    [Personal Site] [Blog] [Facebook]signature

    • נערך על-ידי pituachMVP, Moderator יום שני 09 יוני 2014 20:05
    • סומן כתשובה על-ידי eli789 יום שלישי 10 יוני 2014 07:29
    יום שני 09 יוני 2014 20:02
    מנחה דיון
  • אופס.. שכחתי את כל המשוב המקוצר על הקוד :-)

    1. כדאי להתרגל להשתמש במילה מתודה ולא פונקציה (אלו דברים מעט שונים, לא כל מתודה היא פונקציה)
    המתודה Run אצלך היא אינה פונקציה דרך אגב :-) מכיוון שהיא לא מחזירה ערך.

    2. נעבור בקצרה על ה solution

    אני רואה 2 פרויקטים:
    UI וכן XO.
    * נותן הרגשה טובה של תכנון ממשק ה GUI בנפרד מממשק השירות, יפה :-)

    3. פרויקט UI
    נראה שעשית שימוש בפרוייקט CONSOLE במקור עבור אפליקציית WINDOWS FORMS. אם תסתכל במאפייני הפרויקט שלך תראה שהפרוייקט מוגדר להציג console application כברירת מחדל.
    >> למה לא לעבוד עם Output type: Windows application ?
    >> אני לא מציג את זה כחיסרון בהכרח, אלא כנקודה שיוצרת שאלה גדולה, מה גרם לעבוד יותר קשה?

    הפרויקט כולל טופס אחד פשוט המראה את לוח המשחק שלנו.
    כמובן שיש הפניה לפרויקט השירות UI

    4. שם הקובץ Form1.cs אבל שם המחלקה XOForms
    אישית לא אהבתי את זה. זה מקשה על הבנת הדברים ולא מהווה תיעוד טוב למפתח הבא או אתה בעוד כמה שנים כשתיגש לפרויקט שוב.

    5. ממליץ תמיד כברירת מחדל 
        >> לעבוד עם אייקון ייחודי שלך לפרוייקט. סתם לשם תרגול נחמד ותוצאה יפה יותר.
        >> להגדיר את גרסאות ה Publish Version

    6. כאשר יש לנו מספר גדול של אלמנטים למשל מאות כפתורים ו/או המספר דינמי (ז"א בצורה דינמית מוסיפים או מורידים אלמנטים), אז נוח לפעמים לעבוד עם לולאה שתעבור על כל האלמנטים, אבל לבצע סתם לולאה מיותרת גוזל משאבים מיותרים.
        >> יותר פשוט להצמיד לכל כפתור אירוע שלו שמפנה לאותה מתודה במקום שימוש ב 

            void Button_Click(object sender, EventArgs e)
            {
                foreach (KeyValuePair<string, Button> b in buttonsList)
                    if (((Button)sender).Equals(b))
                        lastCellClicked = int.Parse(b.Key);
                waitForClick.Set();
            }

    למעשה כבר הצמדת אירוע לכל כפתור בצורה דינמית יפה, אז למה בתוך האירוע צריך לולאה על כל האלמנטים?!?
    האם שמת לב שהאירוע כולל את הפרמטר sender ?
    פרמטר זה כולל את הנתונים על האלמנט שהפעיל את האירוע. אפשר מראש להכניס ערך מסוים לכל כפתור למשל ללעשות שימוש במאפיין TEXT ולמספר את הכפתורים מ 1 עד 9 ובמיקום זה לעשות שימוש בערך של הכפתור.
    עתה אפשר לעבוד עם משהו כמו 

                Button _B = (Button)sender;
                lastCellClicked = int.Parse(_B.Text);

    לא בדקתי את הקוד וההתאמה המדויקת לך אבל זה הרעיון בגדול.
    * דרך אגב, זה מקום טוב לעבוד עם בלוק TRY, וגם להוציא בשלב הפיתוח נתון לדיבאג.

    אני מקווה שזה נותן משהו :-)


    [Personal Site] [Blog] [Facebook]signature

    • סומן כתשובה על-ידי eli789 יום שלישי 10 יוני 2014 07:30
    יום שני 09 יוני 2014 20:31
    מנחה דיון
  • בנוגע למאפיין TEXT - לא מתאים במקרה הזה. במידה והכפתור כבר סומן ע"י אחד השחקנים ה- TEXT יכיל את ה- Symbol של אותו שחקן (X או O). במידה ושחקן ילחץ על כפתור שכבר סומן ע"י שחקן אחר, תהליך הבדיקה והאימות מתבצע במחלקות של המשחק ולא ארצה שבכל UI שונה אצטרך לבצע את התהליך בנפרד. תקן אותי אם אני טועה :)

    צודק לגבי השימוש ב TEXT במקרה שלך, העיקר שהבנת את הרעיון ועתה אתה יכול לבחור פתרון מתאים. יש עוד כמה מאפייני שאפשר לנצל להעביר נתונים קבועים בצורה מוסתרת :-) זה חלק ממה שעם הזמן תוכל לזרוק בזמן השינה. למשל השם של האלמנט עצמו יכול לכול לכלול מידע כמו B1 לעומת B2 ואת המידע תמיד אפשר לקבל חזרה על ידי ניתוח הטקסט (REPLACE במקרה הנוכחי). יש עוד שיטות דומות...

    עלה לי רעיון פעם אולי ליצור בירושה סוג חדש של Button ולהוסיף לו מאפיין של ID. יכול להקל על העניינים, אבל יש בעיות גדולות מאלו כרגע ;)

    רעיון מצוין.
    אפשר לממש גם בלי מחלקה חדשה אבל זו בהחלט דרך נקייה במודל האובייקטים (תזכור דרך אגב שמודל האוייקטים לא נועד למיטוב אלא לנוחיות העבודה של המפתח).

    שוב תודה, עזרת לי המון! 

    בכיף

    * דרך ב אל תשכח בסיום לסגור את השרשור על ידי סימון התשובות והצבעה על הודעות מועילות :-)


    [Personal Site] [Blog] [Facebook]signature

    • סומן כתשובה על-ידי eli789 יום שלישי 10 יוני 2014 07:53
    יום שלישי 10 יוני 2014 07:35
    מנחה דיון

כל התגובות

  • שלום אלי

    1. במום סיפורים אם תצרף את הקוד והפרוייקט אנשים יוכלו לבדוק ולראות את הדברים. אני ממליץ לצרף קוד רלווטי בתוכן ההודעה וקובץ פרויקט להורדה בקישור.

    2. אני לא מבין מה הקשר לאירועים ולמה עשית שימוש באירוע על מנת להודיע שהגיע תורו של שחקן מסוים. אחרי מה אתה עוקב ומנטר שאתה רוצה שהאירוע שהאירוע יבוצע? האם יכול להיות שאתה עושה שימוש לא מדוייק במילה השמורה EVENT ?

    שוב פעם הקוד יוכל להבהיר את הדברים...

    3. "התכנית תחכה לאירוע של Click מאחד הכפתורים "

    כאן מגיע בדיוק השימוש באירועים :-) 
    בדוק אם הקישורים הבאים עוזרים לך: 
    http://www.dotnetperls.com/button או את הקישור הבא למשל http://msdn.microsoft.com/en-us/library/system.windows.forms.control.onclick(v=vs.110).aspx

    אתה יכול להוסיף אירוע לכל הכפתורים בתכנית ולקבוע שהאירוע יגרור הפעלה של מתודה כלשהי

    * אנא צרף קודים רלוונטיים ואת הפרויקט כדי להבהיר מה עשית 


    [Personal Site] [Blog] [Facebook]signature

    • סומן כתשובה על-ידי eli789 יום שלישי 10 יוני 2014 07:29
    יום ראשון 08 יוני 2014 12:54
    מנחה דיון
  • דרך אגב, הלוגיקה לא אמורה להיות להפסיק ריצה של מתודה בדרך כלל אלא לקבוע מתי להפעיל בלוק של קוד או מתי לא להפעיל אותו (בלוק של קוב במקרה הנוכחי יכול להיות למשל מתודה חיצונית שמבצעת חלק מהפעולות).

    בכל מקרה הפסקת מתודה נעשית פשוט על ידי החזרת return


    [Personal Site] [Blog] [Facebook]signature

    יום ראשון 08 יוני 2014 12:57
    מנחה דיון
  • שלום אלי

    1. במום סיפורים אם תצרף את הקוד והפרוייקט אנשים יוכלו לבדוק ולראות את הדברים. אני ממליץ לצרף קוד רלווטי בתוכן ההודעה וקובץ פרויקט להורדה בקישור.

    2. אני לא מבין מה הקשר לאירועים ולמה עשית שימוש באירוע על מנת להודיע שהגיע תורו של שחקן מסוים. אחרי מה אתה עוקב ומנטר שאתה רוצה שהאירוע שהאירוע יבוצע? האם יכול להיות שאתה עושה שימוש לא מדוייק במילה השמורה EVENT ?

    שוב פעם הקוד יוכל להבהיר את הדברים...

    3. "התכנית תחכה לאירוע של Click מאחד הכפתורים "

    כאן מגיע בדיוק השימוש באירועים :-) 
    בדוק אם הקישורים הבאים עוזרים לך: 
    http://www.dotnetperls.com/button או את הקישור הבא למשל http://msdn.microsoft.com/en-us/library/system.windows.forms.control.onclick(v=vs.110).aspx

    אתה יכול להוסיף אירוע לכל הכפתורים בתכנית ולקבוע שהאירוע יגרור הפעלה של מתודה כלשהי

    * אנא צרף קודים רלוונטיים ואת הפרויקט כדי להבהיר מה עשית 


    [Personal Site] [Blog] [Facebook]signature

    היי רונן,

    1. ניתן להוריד את הפרויקט דרך כאן.

    2. מצרף קטעי קוד רלוונטיים, לדעתי יהיה קצת קשה להבין בלי לראות את התמונה בשלמותה, אבל אנסה.
    יש לי מחלקה בשם Game, Player ו- Board.
    המחלקה Game מכילה אובייקטים של Player ו- Board. במחלקה Game יש פונקציה בשם Run שבעצם מריצה את המשחק שלב אחר שלב.

    public void Run()
            {
                GameOver isOver = GameOver.False;
    
                /* 1 */ // Print game instructions... 
                publishInstructions();
                
                /* 2 */ // Get the names of the players
                player1.GetName();
                player2.GetName();
    
                // print board for the first time
                board.Print();
    
                do // run as soon as game is not over. 
                {
                    /* 4 */
                    // Play (choose cell and place your symbol)
                    isOver = Play(currentPlayer);
    
                    /* 3 */
                    // print the board. 
                    board.Print();
    
                    /* 5 */
                    // Check if game is over
                    if (isOver != GameOver.False) // if game is over...
                    {
                        switch (isOver)
                        {
                            case GameOver.Winner:
                                declareGameStatus(string.Format("The winner is: {0}", currentPlayer.Name));
                                break;
                            case GameOver.Standoff:
                                declareGameStatus(string.Format("No one wins! -- STANDOFF --"));
                                break;
                        }
                    }
    
    
    
                    /* 6 */
                    // Switch currrent player. 
                    SwitchCurrentPlayer();
    
                } while (isOver == GameOver.False);
    
            }

    בתוך הפונקציה Play מהקוד הנ"ל, מופעלת פונקציה בשם GetCellNumber מתוך אותו currentPlayer שנשלח כפרמטר.
    הפונקציה GetCellNumber שיושבת תחת Player, זורקת event - אני מתסכל על זה כך שהתוכנית בעצם מכריזה "צריך לקלוט מספר תא, תממש את זה איך שאתה באיזה ממשק שאתה רוצה".
        public delegate int CellNumber(Player p);
    
        public class Player
        {
            public event CellNumber getCellNumber;
    
            public int GetCellNumber()
            {
                return getCellNumber(this);
            }
        }
    כאשר ה- event הזה נזרק, ותפסתי אותו בפרוייקט מסוג Console, ביצעתי קליטה של מספר התא הרצוי בעזרת הפונקציה Console.ReadLine. הפונקציה המתינה עד אשר המשתמש יקיש על ENTER ורק אז המשיכה.

            public int GetCellNumber(Player p)
            {
                int cell = 0;
                bool isOk = false;
    
                while (!isOk)
                {
                    Console.Write("{0} - Please enter cell number: ", p.Name);
                    isOk = int.TryParse(Console.ReadLine(), out cell);
    
                    if (!isOk)
                        Console.WriteLine("Please enter only integer numbers!");
                }
    
                return cell;
            }

    הבעיה התחילה כאשר ניסיתי ליצור ממשק למשחק ב- Windows Forms.
    יצרתי את הפונקציה GetCellNumber במחלקה XOForms, ושייכתי אותה לאירוע שנזרק בפונקציה GetCellNumber שנמצאת במחלקה Player. כעת, כל פעם שיגיע תור השחקן לבחור תא רצוי, תופעל הפונקציה GetCellNumber במחלקה XOForms.
    הבעיה: איזה ערך אחזיר ? הערך מתעדכן כאשר מתבצעת לחיצה על כפתור כלשהו. איך אני ממתין עד שהמשתמש ילחץ על כפתור כלשהו כמו שהפונקציה Console.ReadLine ממתינה עד שהמשתמש ילחץ על המקש ENTER ?
    ומכאן הגעתי למחלקה AutoResetEvent ולכך שהיא מקפיאה לי את חלון המשחק.

        public partial class XOForms : Form, IPlayable
        {
            private Game g;
            private Dictionary<string, Button> buttonsList;
            private AutoResetEvent waitForClick;
            private int lastCellClicked;
    
            public XOForms()
            {
                InitializeComponent();
                InitButtonsList();
                waitForClick = new AutoResetEvent(false);
                g = new Game(GetName, Print, PublishInstructions, PrintMessage, GetCellNumber, DeclareGameStatus);
            }
    
            public int GetCellNumber(Player p)
            {
                waitForClick.WaitOne();
                return lastCellClicked;
            }
    
            void Button_Click(object sender, EventArgs e)
            {
                foreach (KeyValuePair<string, Button> b in buttonsList)
                    if (((Button)sender).Equals(b))
                        lastCellClicked = int.Parse(b.Key);
                waitForClick.Set();
    
            }
        }

    מאוד יכול להיות שהתכנון לא נכון מהיסוד. אשמח לשמוע עצות, אחרי הכל עשיתי את כל זה כדי להתקל בבעיות וללמוד.
    תודה על היחס והעזרה ! :)
    יום ראשון 08 יוני 2014 22:06
  • דרך אגב, הלוגיקה לא אמורה להיות להפסיק ריצה של מתודה בדרך כלל אלא לקבוע מתי להפעיל בלוק של קוד או מתי לא להפעיל אותו (בלוק של קוב במקרה הנוכחי יכול להיות למשל מתודה חיצונית שמבצעת חלק מהפעולות).

    בכל מקרה הפסקת מתודה נעשית פשוט על ידי החזרת return


    [Personal Site] [Blog] [Facebook]signature

    היי,

    הכוונה שלי לא הייתה לגרום לסיום פונקציה. אלא להשהות אותה (לגרום לה להמתין עד שיקרה אירוע כלשהו), ולשחרר את ההשהייה לאחר שהופעלה פונקציה ע"י event כלשהו.
    פונקציה1 רצה -> פונקציה1 מושהית -> בוצעה לחיצה ע"י המשתמש על כפתור שגררה את הפעלת פונקציה2 -> פונקציה2 מעדכנת ערכים - > פונקציה2 משחררת את ההשהייה -> פונקציה1 ממשיכה לרוץ ומסתיימת בכך שעושה return לערכים שעודכנו ע"י פונקציה2.
    יום ראשון 08 יוני 2014 22:18
  • טוב הורדתי את הפרויקט, ואם יהיה עדיין צורך אז אני אנסה לעבור עליו בסוף השבע, בינתיים הצצתי מהר ברפרוף על הקוד, אז אני אצרף כמה נקודות שיכולות לעזור באופן כללי.

    * אני מציין שוב שלא עברתי על הקוד כולו ואפילו לא על חלקו לעומק אלא רק בקריאה ראשונה.
    לכן נקודות אלו יכולות להיות לא מתאימות בדיוק למקרה שלך

    לפני שאני מתחיל עם הנקודות אני אתייחס בקצרה לנקודות שאתה מעלה בהודעה האחרונה:

    1. "כאשר ה- event הזה נזרק, ותפסתי אותו בפרוייקט מסוג Console, ביצעתי קליטה של מספר התא הרצוי בעזרת הפונקציה Console.ReadLine. הפונקציה המתינה עד אשר המשתמש יקיש על ENTER ורק אז המשיכה."

    יש הבדל מהותי בין אפליקציית CONSOLE לבין אפליקציית WINDOWS FORMS והוא ה INTERFACE המאפשר קלט/פלט. ב CONSOLE אנחנו מכניסים קלט בעזרת ה SHELL, העכבר למשל לא מהווה בברירת המחדל מקור לקלט, אין GUI שמאפשר לחיצה, שחרור, פוקוס, וכו' בצור דמה לאפליקציה חלונאית. כל האירועים של חלונות אינם רלוונטיים כמובן. לכן באפליקציית CONSOLE כל האפליקציה המתינה לקלט היחיד שהיא מאזינה לו אבל באפליקציה חלונאית יש קלט שמתקבל בצורה שונה.

    2. "הבעיה: איזה ערך אחזיר ? הערך מתעדכן כאשר מתבצעת לחיצה על כפתור כלשהו. איך אני ממתין עד שהמשתמש ילחץ על כפתור כלשהו כמו שהפונקציה Console.ReadLine ממתינה עד שהמשתמש ילחץ על המקש ENTER ?"

    * זו בדיוק הלוגיקה שאמרתי בהודעה קודמת שלי שאני חושב שהיא לא מתאימה. כל החלק של השימוש באירוע (יותר מדויק דרך אגב זה שאתה עובד עם delegate ולא אירוע אם שמתי לב טוב, כל אירוע מבוסס על delegate אבל לא כל שימוש ב delegate הוא אירוע וההבדל גדול מאוד, אבל לא קשור לשאלות שלך כרגע) וכל השימוש ב GetCellNumber הוא פעולה מיותרת לחלוטין לדעתי מכיוון שההתנהלות צריכה להיות הפוכה. לא הטופס צריך לקבל אירוע אלא הטופס צריך לשלוח את האירוע של הלחיצה על הכפתור.

    בכל מקרה אם אתה מחפש פתרון תאורטית לבעיה שאתה מציג אז אתה יכול למשל בנקודה זו להוסיף לכפתורים אירועים של לחיצה ולהמתין לאירוע על מנת להמשיך לבצע פעולות. האירוע לחיצה ימשיך ריצה של קוד אחר שהוא המשך המשחק. בכל מקרה צריך לתפוס את האירוע של הלחיצה על הכפתור כדי להמשיך את הריצה של המשחק בצורה זו או אחרת. 
    * אם היית עובד עם מספר THREAD-ים אז היית יכול לעצור את ה THREAD אבל אם שמתי לב טוב אתה עובד עם ה THRED הראשי בלבד ולכן אין משמעות לעצירה שלו.

    למעשה  אתה לא צריך לבצע כלום, בניגוד לאפליקציית CONSOLE שאם לא מכניסים פעולה אז היא מגיעה לסיום ונסגרת הרי שאפליקציה חלונאית ממשיכה להחזיק את הטופס פתוח והוא יכול לשלוח אירועים כל הזמן. כל הלוגיקה שונה מבחינה זו בין סוגי האפליקציות הנ"ל. פשוט תשאיר את המצב ותמתין לאירוע הלחיצה על הכפתור שיקרא לביצע הפעולה הבאה

    3. AutoResetEvent לא נועד להקפיא חלון אלא את ה THREAD. בקצרה: אין שום צורך בשימוש במחלקה זו לפי מה שאני מבין את הצרכים שלך בכלל. כל החלק הזה צריך לרדת (למרות שאולי תאורטית אפשר להלביש משהו בכוח שיעשה שימוש בכך, משל הפעלת THREAD נוסף בו יבוצע פעולה שתמתין לאירוע... או אפילו שימוש ב THREAD הראשי).

    מדובר במחלקה המייצגת אובייקט המטפל באירוע המתנה של הליכון (כן זה התרגום של THREAD, הליכון מהמילה הליך). אובייקט יכול להיות במצב signaled state או לא. אובייקט במצב signaled לא יגרום להליכון שממתין לאובייקט להיחסם. אובייקט שלא במצב signaled יגרום לכל הליכון שממתין לאובייקט להיחסם עד לרגע שהאובייקט עובר למצב signaled. מטפל אירוע המתנה מודיע על הליכון שנכנס למצב המתנה 

    /// Set: Sets the state of the event to signaled, allowing one or more waiting threads to proceed. (Inherited from EventWaitHandle.)
    /// WaitOne(): Blocks the current thread until the current WaitHandle receives a signal. (Inherited from WaitHandle.)
     /// In XNA Framework, this member is overridden by WaitOne().

    תעבור על הקישורים הבאים ותריץ את הקודים כדי להבין יותר (זה נושא לא פשוט להבנה ראשונה, אבל אחרי שמסתדרים הדברים בראש הוא יעיל מאוד לפעמים):

    http://stackoverflow.com/questions/17613342/signalled-and-non-signalled-state-of-event
    http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.autoresetevent.aspx
    http://msdn.microsoft.com/en-us/library/system.threading.waithandle.aspx
    http://msdn.microsoft.com/en-us/library/system.threading.eventwaithandle.aspx
    http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.aspx



    [Personal Site] [Blog] [Facebook]signature

    • נערך על-ידי pituachMVP, Moderator יום שני 09 יוני 2014 20:05
    • סומן כתשובה על-ידי eli789 יום שלישי 10 יוני 2014 07:29
    יום שני 09 יוני 2014 20:02
    מנחה דיון
  • אופס.. שכחתי את כל המשוב המקוצר על הקוד :-)

    1. כדאי להתרגל להשתמש במילה מתודה ולא פונקציה (אלו דברים מעט שונים, לא כל מתודה היא פונקציה)
    המתודה Run אצלך היא אינה פונקציה דרך אגב :-) מכיוון שהיא לא מחזירה ערך.

    2. נעבור בקצרה על ה solution

    אני רואה 2 פרויקטים:
    UI וכן XO.
    * נותן הרגשה טובה של תכנון ממשק ה GUI בנפרד מממשק השירות, יפה :-)

    3. פרויקט UI
    נראה שעשית שימוש בפרוייקט CONSOLE במקור עבור אפליקציית WINDOWS FORMS. אם תסתכל במאפייני הפרויקט שלך תראה שהפרוייקט מוגדר להציג console application כברירת מחדל.
    >> למה לא לעבוד עם Output type: Windows application ?
    >> אני לא מציג את זה כחיסרון בהכרח, אלא כנקודה שיוצרת שאלה גדולה, מה גרם לעבוד יותר קשה?

    הפרויקט כולל טופס אחד פשוט המראה את לוח המשחק שלנו.
    כמובן שיש הפניה לפרויקט השירות UI

    4. שם הקובץ Form1.cs אבל שם המחלקה XOForms
    אישית לא אהבתי את זה. זה מקשה על הבנת הדברים ולא מהווה תיעוד טוב למפתח הבא או אתה בעוד כמה שנים כשתיגש לפרויקט שוב.

    5. ממליץ תמיד כברירת מחדל 
        >> לעבוד עם אייקון ייחודי שלך לפרוייקט. סתם לשם תרגול נחמד ותוצאה יפה יותר.
        >> להגדיר את גרסאות ה Publish Version

    6. כאשר יש לנו מספר גדול של אלמנטים למשל מאות כפתורים ו/או המספר דינמי (ז"א בצורה דינמית מוסיפים או מורידים אלמנטים), אז נוח לפעמים לעבוד עם לולאה שתעבור על כל האלמנטים, אבל לבצע סתם לולאה מיותרת גוזל משאבים מיותרים.
        >> יותר פשוט להצמיד לכל כפתור אירוע שלו שמפנה לאותה מתודה במקום שימוש ב 

            void Button_Click(object sender, EventArgs e)
            {
                foreach (KeyValuePair<string, Button> b in buttonsList)
                    if (((Button)sender).Equals(b))
                        lastCellClicked = int.Parse(b.Key);
                waitForClick.Set();
            }

    למעשה כבר הצמדת אירוע לכל כפתור בצורה דינמית יפה, אז למה בתוך האירוע צריך לולאה על כל האלמנטים?!?
    האם שמת לב שהאירוע כולל את הפרמטר sender ?
    פרמטר זה כולל את הנתונים על האלמנט שהפעיל את האירוע. אפשר מראש להכניס ערך מסוים לכל כפתור למשל ללעשות שימוש במאפיין TEXT ולמספר את הכפתורים מ 1 עד 9 ובמיקום זה לעשות שימוש בערך של הכפתור.
    עתה אפשר לעבוד עם משהו כמו 

                Button _B = (Button)sender;
                lastCellClicked = int.Parse(_B.Text);

    לא בדקתי את הקוד וההתאמה המדויקת לך אבל זה הרעיון בגדול.
    * דרך אגב, זה מקום טוב לעבוד עם בלוק TRY, וגם להוציא בשלב הפיתוח נתון לדיבאג.

    אני מקווה שזה נותן משהו :-)


    [Personal Site] [Blog] [Facebook]signature

    • סומן כתשובה על-ידי eli789 יום שלישי 10 יוני 2014 07:30
    יום שני 09 יוני 2014 20:31
    מנחה דיון
  • טוב הורדתי את הפרויקט, ואם יהיה עדיין צורך אז אני אנסה לעבור עליו בסוף השבע, בינתיים הצצתי מהר ברפרוף על הקוד, אז אני אצרף כמה נקודות שיכולות לעזור באופן כללי.

    * אני מציין שוב שלא עברתי על הקוד כולו ואפילו לא על חלקו לעומק אלא רק בקריאה ראשונה.
    לכן נקודות אלו יכולות להיות לא מתאימות בדיוק למקרה שלך

    לפני שאני מתחיל עם הנקודות אני אתייחס בקצרה לנקודות שאתה מעלה בהודעה האחרונה:

    1. "כאשר ה- event הזה נזרק, ותפסתי אותו בפרוייקט מסוג Console, ביצעתי קליטה של מספר התא הרצוי בעזרת הפונקציה Console.ReadLine. הפונקציה המתינה עד אשר המשתמש יקיש על ENTER ורק אז המשיכה."

    יש הבדל מהותי בין אפליקציית CONSOLE לבין אפליקציית WINDOWS FORMS והוא ה INTERFACE המאפשר קלט/פלט. ב CONSOLE אנחנו מכניסים קלט בעזרת ה SHELL, העכבר למשל לא מהווה בברירת המחדל מקור לקלט, אין GUI שמאפשר לחיצה, שחרור, פוקוס, וכו' בצור דמה לאפליקציה חלונאית. כל האירועים של חלונות אינם רלוונטיים כמובן. לכן באפליקציית CONSOLE כל האפליקציה המתינה לקלט היחיד שהיא מאזינה לו אבל באפליקציה חלונאית יש קלט שמתקבל בצורה שונה.

    2. "הבעיה: איזה ערך אחזיר ? הערך מתעדכן כאשר מתבצעת לחיצה על כפתור כלשהו. איך אני ממתין עד שהמשתמש ילחץ על כפתור כלשהו כמו שהפונקציה Console.ReadLine ממתינה עד שהמשתמש ילחץ על המקש ENTER ?"

    * זו בדיוק הלוגיקה שאמרתי בהודעה קודמת שלי שאני חושב שהיא לא מתאימה. כל החלק של השימוש באירוע (יותר מדויק דרך אגב זה שאתה עובד עם delegate ולא אירוע אם שמתי לב טוב, כל אירוע מבוסס על delegate אבל לא כל שימוש ב delegate הוא אירוע וההבדל גדול מאוד, אבל לא קשור לשאלות שלך כרגע) וכל השימוש ב GetCellNumber הוא פעולה מיותרת לחלוטין לדעתי מכיוון שההתנהלות צריכה להיות הפוכה. לא הטופס צריך לקבל אירוע אלא הטופס צריך לשלוח את האירוע של הלחיצה על הכפתור.

    בכל מקרה אם אתה מחפש פתרון תאורטית לבעיה שאתה מציג אז אתה יכול למשל בנקודה זו להוסיף לכפתורים אירועים של לחיצה ולהמתין לאירוע על מנת להמשיך לבצע פעולות. האירוע לחיצה ימשיך ריצה של קוד אחר שהוא המשך המשחק. בכל מקרה צריך לתפוס את האירוע של הלחיצה על הכפתור כדי להמשיך את הריצה של המשחק בצורה זו או אחרת. 
    * אם היית עובד עם מספר THREAD-ים אז היית יכול לעצור את ה THREAD אבל אם שמתי לב טוב אתה עובד עם ה THRED הראשי בלבד ולכן אין משמעות לעצירה שלו.

    למעשה  אתה לא צריך לבצע כלום, בניגוד לאפליקציית CONSOLE שאם לא מכניסים פעולה אז היא מגיעה לסיום ונסגרת הרי שאפליקציה חלונאית ממשיכה להחזיק את הטופס פתוח והוא יכול לשלוח אירועים כל הזמן. כל הלוגיקה שונה מבחינה זו בין סוגי האפליקציות הנ"ל. פשוט תשאיר את המצב ותמתין לאירוע הלחיצה על הכפתור שיקרא לביצע הפעולה הבאה

    3. AutoResetEvent לא נועד להקפיא חלון אלא את ה THREAD. בקצרה: אין שום צורך בשימוש במחלקה זו לפי מה שאני מבין את הצרכים שלך בכלל. כל החלק הזה צריך לרדת (למרות שאולי תאורטית אפשר להלביש משהו בכוח שיעשה שימוש בכך, משל הפעלת THREAD נוסף בו יבוצע פעולה שתמתין לאירוע... או אפילו שימוש ב THREAD הראשי).

    מדובר במחלקה המייצגת אובייקט המטפל באירוע המתנה של הליכון (כן זה התרגום של THREAD, הליכון מהמילה הליך). אובייקט יכול להיות במצב signaled state או לא. אובייקט במצב signaled לא יגרום להליכון שממתין לאובייקט להיחסם. אובייקט שלא במצב signaled יגרום לכל הליכון שממתין לאובייקט להיחסם עד לרגע שהאובייקט עובר למצב signaled. מטפל אירוע המתנה מודיע על הליכון שנכנס למצב המתנה 

    /// Set: Sets the state of the event to signaled, allowing one or more waiting threads to proceed. (Inherited from EventWaitHandle.)
    /// WaitOne(): Blocks the current thread until the current WaitHandle receives a signal. (Inherited from WaitHandle.)
     /// In XNA Framework, this member is overridden by WaitOne().

    תעבור על הקישורים הבאים ותריץ את הקודים כדי להבין יותר (זה נושא לא פשוט להבנה ראשונה, אבל אחרי שמסתדרים הדברים בראש הוא יעיל מאוד לפעמים):

    http://stackoverflow.com/questions/17613342/signalled-and-non-signalled-state-of-event
    http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.autoresetevent.aspx
    http://msdn.microsoft.com/en-us/library/system.threading.waithandle.aspx
    http://msdn.microsoft.com/en-us/library/system.threading.eventwaithandle.aspx
    http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.aspx



    [Personal Site] [Blog] [Facebook]signature

    איזו השקעה, מלך ! :)

    1-2. בשורה התחתונה, אתה ממליץ לי בעצם לבטל/לשנות את המתודה "Run" במחלקה Game, ולנהל במחלקה של ה- UI את הסדר בהם הדברים עובדים ?

    למרות שזה קצת מוריד מהמטרה שרציתי להגיע אליה - לכתוב פרוייקט שהוא קופסא שחורה לגמרי, שגם אם יבוא מחר המתכנת ה"טיפש" בעולם ליצור למשחק הזה UI כלשהו, הוא יוכל לעשות את זה בצורה הכי פשוטה שיש, כמעט בלי צורך לחשוב - זה פשוט יכפה עליו את העבודה.
    מצד שני, אני רואה שאין ברירה, אלא לשחרר קצת יותר אפשרות לדינמיקה - כי אני לא יודע איך פועל ה- UI שירצו ליצור למשחק הזה מחר.

    הבנתי אותך נכון ?

    3. הבעיה שכשאני מנסה להקפיא את ההליכון, החלון של המשחק קופא :)
    קראתי קצת על מחלקה שנקראת BackgroundWorker שמוסיפה הליכון נוסף, וכך אולי אוכל להפריד בין חלון המשחק למתודה הממתינה ללחיצה.
    אבל אמרנו שזאת דרך קצת עקומה לפתור את העניין, אז אתעמק בזה בשלב אחר.

    תודה!

    יום שלישי 10 יוני 2014 06:21
  • אופס.. שכחתי את כל המשוב המקוצר על הקוד :-)

    1. כדאי להתרגל להשתמש במילה מתודה ולא פונקציה (אלו דברים מעט שונים, לא כל מתודה היא פונקציה)
    המתודה Run אצלך היא אינה פונקציה דרך אגב :-) מכיוון שהיא לא מחזירה ערך.

    2. נעבור בקצרה על ה solution

    אני רואה 2 פרויקטים:
    UI וכן XO.
    * נותן הרגשה טובה של תכנון ממשק ה GUI בנפרד מממשק השירות, יפה :-)

    3. פרויקט UI
    נראה שעשית שימוש בפרוייקט CONSOLE במקור עבור אפליקציית WINDOWS FORMS. אם תסתכל במאפייני הפרויקט שלך תראה שהפרוייקט מוגדר להציג console application כברירת מחדל.
    >> למה לא לעבוד עם Output type: Windows application ?
    >> אני לא מציג את זה כחיסרון בהכרח, אלא כנקודה שיוצרת שאלה גדולה, מה גרם לעבוד יותר קשה?

    הפרויקט כולל טופס אחד פשוט המראה את לוח המשחק שלנו.
    כמובן שיש הפניה לפרויקט השירות UI

    4. שם הקובץ Form1.cs אבל שם המחלקה XOForms
    אישית לא אהבתי את זה. זה מקשה על הבנת הדברים ולא מהווה תיעוד טוב למפתח הבא או אתה בעוד כמה שנים כשתיגש לפרויקט שוב.

    5. ממליץ תמיד כברירת מחדל 
        >> לעבוד עם אייקון ייחודי שלך לפרוייקט. סתם לשם תרגול נחמד ותוצאה יפה יותר.
        >> להגדיר את גרסאות ה Publish Version

    6. כאשר יש לנו מספר גדול של אלמנטים למשל מאות כפתורים ו/או המספר דינמי (ז"א בצורה דינמית מוסיפים או מורידים אלמנטים), אז נוח לפעמים לעבוד עם לולאה שתעבור על כל האלמנטים, אבל לבצע סתם לולאה מיותרת גוזל משאבים מיותרים.
        >> יותר פשוט להצמיד לכל כפתור אירוע שלו שמפנה לאותה מתודה במקום שימוש ב 

            void Button_Click(object sender, EventArgs e)
            {
                foreach (KeyValuePair<string, Button> b in buttonsList)
                    if (((Button)sender).Equals(b))
                        lastCellClicked = int.Parse(b.Key);
                waitForClick.Set();
            }

    למעשה כבר הצמדת אירוע לכל כפתור בצורה דינמית יפה, אז למה בתוך האירוע צריך לולאה על כל האלמנטים?!?
    האם שמת לב שהאירוע כולל את הפרמטר sender ?
    פרמטר זה כולל את הנתונים על האלמנט שהפעיל את האירוע. אפשר מראש להכניס ערך מסוים לכל כפתור למשל ללעשות שימוש במאפיין TEXT ולמספר את הכפתורים מ 1 עד 9 ובמיקום זה לעשות שימוש בערך של הכפתור.
    עתה אפשר לעבוד עם משהו כמו 

                Button _B = (Button)sender;
                lastCellClicked = int.Parse(_B.Text);

    לא בדקתי את הקוד וההתאמה המדויקת לך אבל זה הרעיון בגדול.
    * דרך אגב, זה מקום טוב לעבוד עם בלוק TRY, וגם להוציא בשלב הפיתוח נתון לדיבאג.

    אני מקווה שזה נותן משהו :-)


    [Personal Site] [Blog] [Facebook]signature

    1. מקובל :)

    2. תודה

    3. הפרויקט תוכנן במקור ל- Console Application. זאת הייתה הדרישה.
    אם תסתכל, באותו פרויקט יש גם מחלקה בשם ConsoleGame, שאם תפעיל אותה המשחק פועל יפה ב- Console.

    כל העניין של הפרדת ה- UI מהלוגיקה היה רעיון שלי. אמנם לא נדרשתי לעשות זאת בפרויקט הנוכחי (מכיוון שרק עכשיו קצת למדנו על ה- WIN FORMS) אך אדרש לעשות זאת במוקדם או במאוחר.
    בקיצור, רציתי לראות אם הקוד שכתבתי באמת מתאים לעבודה גם עם Windows Forms. ואם לא, לראות איפה הטעות כדי שפעם הבאה שאדרש להפריד בין הלוגיקה ל- UI אהיה קצת יותר מנוסה :)

    4. צודק. באופן כללי כמו שאמרתי היצירה של ה- Form הייתה רק לניסוי קטן... אבל זאת לא סיבה :)

    5. גרסאות ה- publish נשמע כמו משהו שימושי שלא נחשפתי אליו עדיין, אקרא על כך, תודה.

    6. צודק בהחלט. כרגע הכפתורים היחידים הם הכפתורים של הלוח. המחשבה שלי הייתה שמחר יכול להיות שיתווספו כפתורים אחרים בטופס, אשר יופנו לאותה מתודה המופעלת ע"י האירוע Click. אז בלולאה פשוט בדקתי אם הכפתור שהפעיל את המתודה הוא אחד מכפתורים השייכים ללוח.
    אני מודה, מחשבה טיפשית :). כל מה שצריך לעשות זה לייעד את המתודה הזאת לכפתורים השייכים ללוח, ואת שאר הכפתורים לשייך למתודה אחרת.

    בנוגע למאפיין TEXT - לא מתאים במקרה הזה. במידה והכפתור כבר סומן ע"י אחד השחקנים ה- TEXT יכיל את ה- Symbol של אותו שחקן (X או O). במידה ושחקן ילחץ על כפתור שכבר סומן ע"י שחקן אחר, תהליך הבדיקה והאימות מתבצע במחלקות של המשחק ולא ארצה שבכל UI שונה אצטרך לבצע את התהליך בנפרד. תקן אותי אם אני טועה :)

    עלה לי רעיון פעם אולי ליצור בירושה סוג חדש של Button ולהוסיף לו מאפיין של ID. יכול להקל על העניינים, אבל יש בעיות גדולות מאלו כרגע ;)

    שוב תודה, עזרת לי המון!


    • נערך על-ידי eli789 יום שלישי 10 יוני 2014 07:12
    יום שלישי 10 יוני 2014 07:10
  • איזו השקעה, מלך ! :)

    תודה :-)

    1-2. בשורה התחתונה, אתה ממליץ לי בעצם לבטל/לשנות את המתודה "Run" במחלקה Game, ולנהל במחלקה של ה- UI את הסדר בהם הדברים עובדים ?<

    את המקור לאירועים... כן בהחלט. זו השיטה בעבודה עם הפרדה של GUI מהחלק של ה SERVICE. החישובים/הלוגיקה נעשים בשירות (במקרה שלך הרעיון מתאים  בערך לחלק של ה XO), והממשק המתנהל מול המשתמש שהוא ה GUI כולל את האירועים של המשתמש (ובמקרה שלך זה ה UI). המימוש שלך מעט מסורבל והלוגיקה צריכה שיפור, אבל זה הרעיון של ההפרדה.

    למרות שזה קצת מוריד מהמטרה שרציתי להגיע אליה - לכתוב פרוייקט שהוא קופסא שחורה לגמרי, שגם אם יבוא מחר המתכנת ה"טיפש" בעולם ליצור למשחק הזה UI כלשהו, הוא יוכל לעשות את זה בצורה הכי פשוטה שיש, כמעט בלי צורך לחשוב - זה פשוט יכפה עליו את העבודה.

    חס וחלילה!!!

    לא סתם כתבתי לך בהודעה מעל שאני אוהב את ההפרדה! אל תהרוס אותה אם אתה יכול. זה המבנה הנכון והמומלץ ואתה צריך באמת לשמור על הרעיון של ההפרדה והגמישות לכל ממשק.

    את החלק של ה XO היה בעקרון אפילו הרבה יותר טוב לבצע ממש בצורה מופרדת ובלתי תלויה, בעזרת אפליקציה נפרדת מסוג WCF או שירות אינטרנט וכו'

    ההפרדה נכונה אבל המימוש צריך יותר מחשבה. מימוש גמיש מחייב מחשבה מעמיקה יותר :-)
    ארכיטקטורה של מערכת היא הרמה היותר גבוהה בפיתוח ולא כתיבת הקוד או הכרה בע"פ שמות של מחלקות שמייקרוסופט מספקים לנו בצורה מובנית.

    * בשלב ראשוני של הלימוד הייתי מציע קודם להגיע לאפליקציה עובדת. בשלב הבא לחשוב על ההפרדה הנכונה. הרעיון המרכזי צריך להיות שממשק ה GUI מספק לממשק השירות את האירועים והקשר למשתמש ומעביר אל השירות בקשות למידע מחושב כזה או אחר. השירות מבצע את החישובים לפי פניות שהוא מקבל ומחזיר תגובות למי שפונה אליו.

    * תזכור גם באפליקציית ה CONSOLE היה צורך בתגובה מהלקוח. הלוגיקה שתתאים באפליקציה חלונאית תתאים גם לממש CONSOLE. ההבדל היסודי היחיד הוא ארגון האירועים בצד של ה GUI ולא של השירות (חלון ה SHELL הוא ה GUI של אפליקציית CONSOLE).


    מצד שני, אני רואה שאין ברירה, אלא לשחרר קצת יותר אפשרות לדינמיקה - כי אני לא יודע איך פועל ה- UI שירצו ליצור למשחק הזה מחר.

    תמיד יש ברירה!!!

    רק צריך לפעמים יותר לחשוב, ואולי מעט יותר ניסיון. ראה הערות מעל...

    הבנתי אותך נכון ?

    כן... בערך. ראה הערות למעלה :-)

    3. הבעיה שכשאני מנסה להקפיא את ההליכון, החלון של המשחק קופא :)

    נכון, זה בדיוק ה שכתבתי למעלה :-) אתה עובד עם תהליכון בודד שהוא התהליכון הראשי של האפליקציה. ברור שאם אתה עוצר אותו אז עצרת את האפליקציה :-)


    [Personal Site] [Blog] [Facebook]signature

    יום שלישי 10 יוני 2014 07:23
    מנחה דיון
  • בנוגע למאפיין TEXT - לא מתאים במקרה הזה. במידה והכפתור כבר סומן ע"י אחד השחקנים ה- TEXT יכיל את ה- Symbol של אותו שחקן (X או O). במידה ושחקן ילחץ על כפתור שכבר סומן ע"י שחקן אחר, תהליך הבדיקה והאימות מתבצע במחלקות של המשחק ולא ארצה שבכל UI שונה אצטרך לבצע את התהליך בנפרד. תקן אותי אם אני טועה :)

    צודק לגבי השימוש ב TEXT במקרה שלך, העיקר שהבנת את הרעיון ועתה אתה יכול לבחור פתרון מתאים. יש עוד כמה מאפייני שאפשר לנצל להעביר נתונים קבועים בצורה מוסתרת :-) זה חלק ממה שעם הזמן תוכל לזרוק בזמן השינה. למשל השם של האלמנט עצמו יכול לכול לכלול מידע כמו B1 לעומת B2 ואת המידע תמיד אפשר לקבל חזרה על ידי ניתוח הטקסט (REPLACE במקרה הנוכחי). יש עוד שיטות דומות...

    עלה לי רעיון פעם אולי ליצור בירושה סוג חדש של Button ולהוסיף לו מאפיין של ID. יכול להקל על העניינים, אבל יש בעיות גדולות מאלו כרגע ;)

    רעיון מצוין.
    אפשר לממש גם בלי מחלקה חדשה אבל זו בהחלט דרך נקייה במודל האובייקטים (תזכור דרך אגב שמודל האוייקטים לא נועד למיטוב אלא לנוחיות העבודה של המפתח).

    שוב תודה, עזרת לי המון! 

    בכיף

    * דרך ב אל תשכח בסיום לסגור את השרשור על ידי סימון התשובות והצבעה על הודעות מועילות :-)


    [Personal Site] [Blog] [Facebook]signature

    • סומן כתשובה על-ידי eli789 יום שלישי 10 יוני 2014 07:53
    יום שלישי 10 יוני 2014 07:35
    מנחה דיון