שאלת מבחן בתכנות מונחה עצמים - אוניברסיטת בר-אילן 2016 - אובייקטים

נרצה להרחיב את משחק ה-Arkanoid שפיתחנו במהלך הסמסטר כך שיתמוך גם ב"מצב רטרו", בו המשחק כולו יראה בצבעי שחור-לבן (או למעשה, "גוונים של אפור", Grayscale) במקום בגרסה הצבעונית. ניתן, כמובן, לבקש ממעצבי המסכים והשלבים של המשחק לתכנן אותם מראש בגוונים של אפור, אבל אנו מעוניינים בפתרון שלא יצריך זמן של מעצב גרפי, ושיעבוד מול כל אוסף שלבים שהוא. בעת הפעלת המשחק, נרצה שיהיה מתג (command line argument) שיקבע האם המשחק יעבוד "כרגיל" או במצב גוונים-של-אפור.

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


ניתן להניח כי קיים רכיב תוכנה המקבל אובייקט מסוג java.awt.Color ומחזיר אובייקט מאותו הסוג המייצג את גוון האפור המתאים עבורו, וכן רכיב תוכנה המקבל אובייקט תמונה ומחזיר אובייקט תמונה אחר בגוונים של אפור.
(למתעניינים, הנספח שבסוף המבחן מתאר כיצד עובד אלגוריתם ההמרה לגווני אפור.)
העתק שאלה
שתף שאלה
סמן כחשוב
סמן כבוצע
אוניברסיטת בר-אילןמועד א2016סמסטר ב
אובייקטיםמחלקותתבניות עיצוב
במקום לשנות את אופן הציור של כל אובייקט בנפרד, חשבו כיצד ניתן ליירט ולשנות את התמונה הסופית המורכבת מכל האובייקטים, רגע לפני שהיא מוצגת על המסך.
הפתרון המיטבי לבעיה זו יתבסס על עיקרון של ריכוז השינוי במקום אחד, מבלי לגעת בקוד הקיים של רכיבי המשחק השונים (כגון הכדור, הלבנים, וכו'). המטרה היא ליירט את התוצאה הסופית של הציור, ולהחיל עליה את אפקט גווני האפור באופן שקוף לשאר חלקי המערכת. הפתרון אינו משתמש בירושה ועומד במגבלות השאלה.

הטכניקה המתאימה לכך היא שימוש ב-Off-Screen Buffer (חוצץ מחוץ למסך) בשילוב עם פילטר גרפי. הרעיון הוא לצייר את כל סצנת המשחק לא לתצוגה הראשית, אלא לתמונה זמנית בזיכרון, ורק לאחר מכן, אם יש צורך, להחיל על התמונה הזו את פילטר גווני האפור ולצייר את התוצאה הסופית למסך.


שלבי הפתרון:
1. זיהוי נקודת הציור המרכזית: יש לאתר את המתודה האחראית על ציור כלל הרכיבים למסך. ביישומי Swing ב-Java, זוהי בדרך כלל המתודה paintComponent(Graphics g) של רכיב ה-JPanel הראשי של המשחק.

2. הוספת לוגיקה מותנית: בתוך מתודת הציור המרכזית, נבדוק את הדגל שנקבע בעת הפעלת התוכנית (למשל, isRetroMode).

3. ציור לחוצץ: אם isRetroMode פעיל, ניצור אובייקט java.awt.image.BufferedImage בגודל של אזור המשחק. נקבל ממנו את ההקשר הגרפי (ה-Graphics שלו), ונבצע את כל פעולות הציור המקוריות על ההקשר הגרפי של התמונה הזמנית, במקום על ההקשר הגרפי המקורי שהמתודה קיבלה.

4. החלת הפילטר: לאחר שכל הסצנה הצבעונית מצוירת על ה-BufferedImage, נשתמש ברכיב שהוזכר בשאלה (או ברכיב מובנה ב-Java כמו java.awt.image.ColorConvertOp עם ColorSpace.CS_GRAY) כדי להמיר את התמונה כולה לגווני אפור. פעולה זו לוקחת את התמונה הצבעונית ומייצרת גרסה שלה בגווני אפור.

5. ציור התוצאה למסך: בשלב הסופי, נצייר את ה-BufferedImage (שכעת הוא בגווני אפור) על ההקשר הגרפי המקורי של הרכיב, בפעולת drawImage אחת.


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


תבניות עיצוב (Design Patterns):
הפתרון המוצע הוא יישום של תבנית ה-Decorator (עיטור). על אף שאיננו יוצרים מחלקה חדשה GrayscaleDecorator שיורשת מ-Graphics (בשל המגבלה על שימוש בירושה), הרעיון המרכזי של התבנית נשמר. אנו "עוטפים" את התוצאה של תהליך הציור המקורי בפונקציונליות נוספת (הפילטר) מבלי לשנות את התהליך עצמו. רכיבי המשחק מציירים את עצמם כרגיל, ואינם מודעים כלל לכך שהפלט הסופי שלהם יעבור עיבוד נוסף. זוהי התכונה המרכזית של תבנית ה-Decorator: הוספת אחריות לאובייקט באופן דינמי ושקוף.


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