ConnectFour Documentation
מגישים: חן פרנקל ת.ז. 021610860
רון
פרנקל ת.ז. 021610852
הקדמה
תיאור הממשק:
התפריט: התפריט מורכב מ 3 תתי תפריטים: File, Options, Help . תחת File נמצאות האפשרויות להתחלת משחק חדש (שאפשרית גם
ע"י לחיצה על F2 ) ויציאה מהתוכנה.
תחת Options ניתן לבחור את סוג השחקן של
כל צד (אדום ושחור) דרך אפשרות Players וקביעת מאפייני המשחק הנוספים דרך preferences.
דיאלוג preferences: מאפשר בחירה של סוגי
השחקנים, טעינת משקולות מקובץ, ושליטה בערכי הקבועים המשמשים את תהליך המשחק
והלמידה.
סוגי השחקנים האפשריים הם שחקן לומד,
שחקן לא לומד (אבל משתמש במשקולות שנלמדו) עם או בלי שימוש ב MinMax, ושחקן רנדומלי.
קבועי הלמידה הניתנים לשינוי הם: discount rate – γבסכום:
Qπ(s,a)
= Eπ{Σ(γ^i * rt+k+1) | st=s,
at=a}
Step size – α בנוסחאת העידכון:
Qπ(st,at) ß Qπ(st,at) + α[rt+1 + γmaxa Qπ(st,a) - Qπ(st,at)]
חשוב: step size מחולק במספר ה attributes שאלג' הלמידה משתמש בהם ,
14. |
ו epsilon – ההסתברות בחרית צעד רנדומלי
באלגוריתם השחקן הלומד.
קבועים נוספים הם ההסתברות להחלפת צדדים
של השחקנים (לאחר סיום כל משחק) בהרצת משחקים של מחשב מול מחשב, ועומק החיפוש בMinMax עבור שחקן מסוג מתאים.
ניתן לקרוא את קבצי המשקולות בעזרת weights.exe.
השימוש: weights.exe <filename>
ביצוע הלמידה:
נעשה ע"י הרצת משחקים כשלפחות אחד
הצדדים הוא שחקן לומד. הלמידה נעשית ע"י אלגוריתם Gradient
Descent Q-learning,
עם שימוש בפונ' לינארית לקירוב Q(s, a).
בכדי ללמד את התוכנה יש לבחור במשחק
מחשב מול מחשב כשלפחות אחד הצדדים הוא שחקן לומד (ניתן לטעון את המשקולות
ההתחלתיים מקובץ) ולהתחיל משחק חדש. בשלב זה יופיע דיאלוג לבחירת מספר המשחקים
שהמחשבים ישחקו זה נגד זה. בסוף הרצת המשחקים יופיע דיאלוג עבור כל שחקן לומד לשמירת
המשקולות שנלמדו לקובץ. השחקנים מחליפים צדדים במהלך הריצה (או לפחות עלולים לעשות
זאת אם ההסת' לכך > 0 ) אבל בסוף ההרצה הם חוזרים למקומם ההתחלתי ורק אז
נשמרים, כלומר בחירת המאפיינים של השחקנים ב preferences מתאימה לסדר השמירה של
השחקנים בסיום הריצה.
הלמידה
וייצוג המשחק:
אלגוריתם הלמידה שבו בחרנו הוא Gradient Descent Q-learning . הסיבה שבחרנו באלג' שמסתכל על פונ' קירוב ל Q(s,a) היא שמרחב המצבים אקספוננציאלי ביחס לגודל
"הקלט" כלומר הלוח ולכן זה לא פרקטי לנסות לבנות את Q(s,a) עצמה. בחרנו בפונ' קירוב לינארית. כמו כן בחרנו ב Q-learning
מכיוון שידוע שביצועיו טובים והוא מוכח כמתכנס. מימשנו את האלגוריתם כ ε-greedy,
כלומר בהסתברות ε האלגוריתם בוחר מהלך באופן רנדומי (רעיון ה- exploration).
האלגוריתם שהשתמשנו בו מתואר
בפסאודו-קוד בספר של Richard S. Sutton ו Andrew G. Barto בפרק 8.4 באיור 8.9 .
הקישור :
http://www.cs.ualberta.ca/%7Esutton/book/ebook/node89.html
רשימת ה attributes הקיימים בתוכנה (לפי שמות המחלקות בקוד):
Att2Threats, Att3InRow, Att4InRow, AttOddThreats,
AttEvenThreats, Att2ThreatsRival, Att3InRowRival,
Att4InRowRival, AttOddThreatsRival, AttEvenThreatsRival, Att3InRowWin, Att3InRowWinRival,
AttSlotCreates2Threats, AttSlotCreates2ThreatsRival
כאשר התכונות שמסתיימות ב Rival
זהות למתאימות להם ללא הסיומת מלבד העובדה שהן מסתכלות על הלוח בתור השחקן היריב.
כעת נסביר את מהות התכונות.
Att2Threats – הכמות של זוגות של משבצות
ריקות, אחת מעל השניה, שמהוות איום עתידי כלומר ע"י מילוי משבצת ריקה זו (כל
אחת מהשתים) בכלי של השחקן המתאים נקבל 4 בשורה. הכמות מחולקת בקבוע על מנת
שהתכונה תחזיר ערך בין 0 ל 1.
דוגמה: XXX_O
OXX_X כאשר '_' הוא משבצת ריקה.
Att3InRow – מספר המשבצות הריקות
המחוברות ל3 משבצות המכילות כלי של השחקן, כלומר משבצות ריקות שאם ימולאו בשחקן
המדובר יצרו 4 בשורה עבור שחקן זה. מספר זה מחולק ב
42 בכדי להחזיר ערך בין 0 ל 1.
דוגמאות: XX_X
, XXX_ ,
|
|
|
O |
|
|
O |
|
|
O |
|
|
_ |
|
|
|
Att4InRow – ערך תכונה זו הוא 1 אם יש בלוח 4 בשורה של השחקן המדובר ו 0 אם אין.
AttOddThreats – כמו Att3InRow אבל מסתכלים על משבצות ריקות
רק בשורות אי זוגיות כשמספר השורה הנמוכה ביותר הוא 1.
AttEvenThreats - כמו Att3InRow אבל מסתכלים על משבצות ריקות
רק בשורות זוגיות כשמספר השורה הנמוכה ביותר הוא 1.
Att3InRowWin – כמו Att3InRow אבל מסתכלים רק על משבצות ריקות
שניתן לשים בהן כלי כרגע, כלומר המשבצת שנמצאת שורה אחת מתחת למשבצת הנבדקת מלאה
(זהו איום המביא לנצחון בצעד אחד). מספר המשבצות
הנ"ל מחולק ב 7 בכדי להחזיר ערך בין 0 ל 1.
דוגמה: O_OO → אם השחקן O יניח כלי במשבצת הריקה הוא
ינצח
XXOX
AttSlotCreates2Threats – מספר המשבצות הריקות שניתן
להניח בהן כלי (המשבצת באותה עמודה שנמצאת שורה מתחתיה מלאה) כך שיווצרו שני
איומים מידיים שהכלי משתתף בהם (כלומר משבצות שניתן להניח בהם כלי בדומה למשבצת
המקורית). משבצת המקיימת תכונה זו יכולה להוביל לנצחון בשני מהלכים תכונה זו
מאפשרת לראות מלכודת עתידית לפני שמאוחר מדי. מספר זה מחולק ב 7 בכדי להחזיר ערך
בין 0 ל 1.
דוגמאות:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
O |
* |
O |
_ |
|
|
|
X |
O |
X |
X |
|
|
|
X |
O |
X |
X |
|
אם נמלא את * ב O נקבל איום מיידי מאונך בעמודה
4 ואיום מיידי מאוזן בשורה 3 (כשסופרים מלמטה ומשמאל).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_ |
X |
* |
X |
_ |
|
אם נמלא את * ב X נקבל שני איומים מידיים משני
צדדי הסידור.
ההגיון מאחורי התכונה Att2Threats
היא שכאשר יש איום מעל איום ומגיעים למצב שאפשר לשים כלי באיום התחתון אז אם
איומים אלה בלי הגבלת הכלליות שייכים ל O אז אם O יניח כלי באיום התחתון הוא
ניצח, לכן X
ירצה להניח בו כלי אחרת יפסיד. אבל אז האיום העליון נגיש ואז תורו
של O שיניח שם כלי וינצח.
ההגיון מאחורי AttOddThreats ו- AttEvenThreats הוא שמסתבר, לפי מידע שמצאנו,
שהאיומים בשורות אי זוגיות (השורה הנמוכה ביותר ממוספרת 1) הם יותר משמעותיים
כשמגיעים לסוף המשחק (אז יש מעט מקומות לשים בהם כלים והסיום הקרב כבר די קבוע).
ישנן שתי תכונות קריטיות לבחירת מהלך.
תכונה אחת היא Att4InRow. תכונה זו הכרחית על מנת שהמחשב "ירצה
לנצח". נוכחנו לדעת שהתכונה המקבילה Att4InRowRival אינה חשובה ולמעשה חסרת
משמעות כיוון שהיא מופיעה רק לאחר שהצד השני ניצח לכן לעולם לא ניתקל בה מכיוון
שבלמידה האחרונה משתמשים בערכי התכונות של המצב הלפני אחרון יחד עם הפעולה
שביצענו. לא הורדנו אותה מכיוון שלא רצינו להתעסק עם קבצי המשקולות. ב Att4InRow,
לעומת זאת, ניתקל בלמידה האחרונה בגלל שמחשיבים גם את הפעולה שביצענו.
התכונה השניה היא Att3InRowWinRival. תכונה זו הכרחית מכיוון שהיא
בעצם התחליף של Att4InRowRival וה"נגדית" של Att4InRow במובן שנראה אותה בדיוק לפני
הפסד וללא תכונה זו המחשב לא "ידע" שהוא חייב לחסום את השלשה אחרת כנראה
היא תהפוך לרביעיה ונפסיד.
תהליך
האימון:
אימנו את השחקן שלנו בכמה דרכים ויצרנו
כך כמה סטים של משקולות המפורטים להלן: knowledge.data, knowledge_other.data, player1.data,
player2.data
אנחנו חושבים ש player1.data,
player2.data הם
בעלי האסטרטגיה (קירוב של Q(s,a) ) הכי מוצלחת. למרות שקשה
להבחין בהבדלים ברמה.
knowledge:
הקבועים:
discount rate = 0.95
step size = 0.2 ( / weights amount)
epsilon = 0.1
probability of switching sides = 0.3
פרטי ההרצות:
הרצות המשחקים הן ברצפים נגד שחקן
רנדומלי בהתחלה, ואח"כ נגד שחקן לא לומד ללא MinMax.
משקולות שחקן לומד (התחלתיות) |
משקולות שחקן נגדי |
מספר משחקים |
קובץ תוצאה (משקולות) |
ללא קובץ, משקולות שווים |
שחקן רנדומלי |
5000 |
knowledge_1.data |
knowledge_1.data |
knowledge_1.data |
20000 |
knowledge_2.data |
knowledge_2.data |
knowledge_1.data |
20000 |
knowledge_3.data |
knowledge_3.data |
knowledge_2.data |
20000 |
knowledge_4.data |
knowledge_4.data |
knowledge_2.data |
20000 |
knowledge_5.data |
knowledge_5.data |
knowledge_3.data |
20000 |
knowledge_6.data |
knowledge_6.data |
knowledge_4.data |
20000 |
knowledge_7.data |
knowledge_7.data |
knowledge_5.data |
30000 |
knowledge_8.data |
knowledge_8.data |
knowledge_5.data |
20000 |
knowledge_9.data |
knowledge_9.data |
knowledge_6.data |
50000 |
knowledge_10.data |
knowledge_10data epsilon=0.07 |
knowledge_6.data |
50000 |
knowledge_11.data |
knowledge_11.data epsilon = 0.1 |
knowledge_8.data |
50000 |
knowledge.data |
knowledge_other:
הקבועים:
discount rate = 0.95
step size = 0.25 ( / weights amount = 14)
epsilon = 0.1
probability of switching sides = 0.3
פרטי ההרצות:
הרצות המשחקים הן ברצפים נגד שחקן רנדומלי בהתחלה, אח"כ נגד שחקן לא לומד ללא MinMax ובסוף נגד שחקן לומד שלא שומרים את המשקולות שלמד.
משקולות שחקן לומד (התחלתיות) |
משקולות שחקן נגדי |
מספר משחקים |
קובץ תוצאה (משקולות) |
ללא קובץ, משקולות שווים |
שחקן רנדומלי |
5000 |
knowledge_1.data |
knowledge_1.data |
knowledge_1.data |
20000 |
knowledge_2.data |
knowledge_2.data |
knowledge_1.data |
20000 |
knowledge_3.data |
knowledge_3.data |
knowledge_2.data |
20000 |
knowledge_4.data |
knowledge_4.data |
knowledge_2.data |
20000 |
knowledge_5.data |
knowledge_5.data |
knowledge_3.data |
20000 |
knowledge_6.data |
knowledge_6.data |
knowledge_4.data |
20000 |
knowledge_7.data |
knowledge_7.data |
knowledge_6.data כשחקן לומד |
250000 |
knowledge_other.data |
player1vs. player2:
הקבועים:
discount rate = 0.95
epsilon = 0.1
probability of switching sides = 0 players stay
in their sides
פרטי ההרצות:
ההרצות בוצעו ללא החלפות צדדים כש player1 בצד הראשון (אדום) ו player2 בצד השני (שחור).
שני הצדדים לומדים. האעיון היה ליצור
שני סטים של משקולות שיתמקצעו לכל צד. כמו כן הרעיון מאחורי הורדת ה step size
משקולות player1 |
משקולות player2 |
מספר המשחקים |
step size (יחולק ב 14) |
קובץ תוצאה של player1 |
קובץ תוצאה של player2 |
knowledge_1.data לקוח מ knowledge |
כמו player1 |
10000 |
0.2 |
player1_0.data |
player2_0.data |
player1_0.data |
player2_0.data |
150000 |
0.2 |
player1_1.data |
player2_1.data |
player1_1.data |
player2_1.data |
150000 |
0.1 |
player1_2.data |
player2_2.data |
player1_2.data |
player2_2.data |
100000 |
0.05 |
player1.data |
player2.data |
Code
Documentation
הסבר כללי:
הקוד מורכב משלושה חלקים עיקריים: GUI , thread לולאת
המשחק, והמחלקות המהוות את שחקן המחשב.
ה GUI:
ה GUI מכיל את הממשק הגרפי שכולל
שליטה במשחק ובמאפייני המשחק (נשמרים במחלקה prefDialog ) והשחקנים ואת מבנה הנתונים המהווה את מצב המשחק. מצב המשחק
מיוצג ע"י int board[ROWS][COLS] - זוהי מטריצת הלוח (כל
תא יכול להכיל PLAYER1, PLAYER2, EMPTY ) , turnOfPlayer – תור השחקן הנוכחי (מקבל PLAYER1, PLAYER2 ), ו int gameOn שמכיל את מספר המשבצות החופשיות שנותרו או אפס אם
המשחק הסתיים (ומאותחל ל 42 שזהו מספר המשבצות בלוח). כל אלו ביחד מייצגים באופן
מלא את מצב משחק.
פרט לכך ה GUI מכיל את כל הפונ' המטפלות
בחלונות ובאינטראקציה עם המשתמש. ה GUI אחראי על כל האינטראקיצה עם המשתמש ולכן הוא גם
אחראי לשליטה בשחקן אנושי, כלומר על הנחת כלי של שחקן אנושי על הלוח. בנוסף ה GUI
אחראי על התחלת משחק חדש כולל איתחול מבני הנתונים הדרושים למשחק, והרצת thread
לולאת המשחק.
thread לולאת המשחק:
ממומש בפונקציה gameLoop( ). פונ' זו מקבלת מקבלת את מספר
המשחקים להרצה ברצף (1 אם יש שחקן אנושי וכמות ניתנת לבחירה אם שני הצדדים הם
שחקני מחשב) ורצה כ thread נפרד. היא אחראית על הרצת המשחקים הדפסת נתונים על המשחקים
לחלון המשחק ושמירה של המשקולות שנלמדו עבור כל שחקן מחשב לומד.
בכל התחלת "רצף" משחקים ה GUI סוגר
את ה thread הישן (אם עדיין פעיל) ופותח thread חדש (לצורך כך מתבצע סינכרון
בין ה GUI ל gameLoop).
מחלקות שחקן המחשב:
מחלקות אלו נחלקות לשתי קבוצות. קבוצה
ראשונה מכילה את המחלקה CompPlayer המייצגת את שחקן המחשב,
והקבוצה השניה מורכבת מהמחלקה Attribute וכל המחלקות היורשות ממנה (כל
המחלקות המתחילות ב Att…)
ומהווה את קבוצת התכונות של הלוח (כל מחלקה מחשבת feature של הלוח שיכול להיות תלוי גם
בשחקן).
המחלקה CompPlayer מכילה מערך של Attribute-ים לצורך הערכת הלוח ואת כל
הנתונים הדרושים לתהליך הלמידה ולביצוע מהלך. נתונים אלו הם: וקטור המשקולות,
וקטור הערכים של ה attribute-ים של הזוג (state, action) האחרון, קבועי תהליך הלמידה, עומק החיפוש ב MinMax, הצד של השחקן (אדום או שחור), וסוג השחקן.
אובייקט של המחלקה מהווה שחקן מחשב
והשימוש בו הוא בעזרת הפונ' move( ) . קריאה לפונ' זו מחזירה את מהלך השחקן. הפונ' move( )
קוראת לאחת מפונ' המשחק האפשריות לפי סוג השחקן (לומד, לא לומד עם MinMax, לא לומד בלי MinMax,
ורנדומלי). בכדי לבצע את המהלך הראשון יש לקרוא לפונ firstMove( ) ורק במהלכים הבאים לקרוא ל move(
).
השימוש במחלקה Attribute וצאצאיה (שנעשה בחישוב
התכונות ב CompPlayer) הוא ע"י קריאה לפונ' calculate
שמחזירה את ערך התכונה המיוצגת ע"י המחלקה.
כל מחלקה היורשת מ Attribute
חייבת לממש את calculate (זוהי פונ' אבסטרקטית, pure
virtual, ב Attribute).
פונקציות
חשובות:
פונקציות GUI:
int APIENTRY WinMain( )
פונקצית הכניסה לתוכנה. זוהי הופנ
הראשונה שרצה והיא אחראית על פתיחת חלון המשחק.
LRESULT CALLBACK WndProc(
)
פונקציה זו נקראת עבור כל ארוע שקורה
בחלון המשחק ( callback function) והיא אחראית על הטיפול בארועים (למשל לחיצה על
כפתור עכבר או הודעה על צורך בציור מחדש של החלון).
פונקציה זו אחראית גם על ביצוע מהלך של
שחקן אנושי ע"י קליטת לחיצה על כפתור עכבר שמאלי והנחת כלי השחקן במקום(במידה
והמהלך חוקי).
בנוסף פונ' זו אחראית גם על התחלת משחק
חדש (איתחול מבני הנתונים ופתיחת thread של לולאת המשחק).
int paintBoard(
)
פונ' זו מציירת את הלוח בהתאם למצבו
(לפי board[][]) וחץ בעמודה בה נמצא העכבר.
פונקציות לשימוש לולאת המשחק:
int winStatus(int player, int x, int y)
פונקציה הבודקת אם השחקן player
ניצח. פונ' זו נקראת לאחר שהונח כלי של player במקום (x, y) בלוח. הפונ' למעשה בודקת אם מקום (x, y) משתתף ברביעיה של כלי player.
void newGame( )
מאתחלת את מבני הנתונים האחראים על משחק
בכדי להתחיל משחק חדש. היא נקראת ע"י gameLoop( ) בכל פעם שמתחילים משחק חדש ברצף.
DWORD WINAPI gameLoop( )
זוהי פונ' thread לולאת המשחק. כפי שתואר לעיל
היא מריצה את רצף המשחקים המבוקש. הפונ' מריצה את הרצף ולאחר מכן מדפיסה נתונים
עליו ואם יש צורך שומרת משקולות של שחקן לומד. הרצת משחק נעשית באופן הבא: ראשית
בודקים אם השחקן שצריך לבצע מהלך הוא אנושי או מחשב. אם הוא אנושי נותנים לו לבצע
את הצעד, ואם הוא מחשב מבקשים ממנו עמודה להניח בה כלי. לאחר מכן בודקים אם יש
ניצחון אם כן פועלים בהתאם לסיים את המשחק ואם לא אז מחליפים את תור השחקן וחוזרים
לביצוע מהלך.
אם שני השחקנים מסוג מחשב אז בין משחק
למשחק מתבצעת החלפת מקומות (בהסתברות נתונה) של השחקנים.
פונקציות שחקן המחשב:
(במחלקה CompPlayer )
int move( )
פונקציה זו מחזירה צעד (עמודה לשים בה
כלי) לביצוע של שחקן המחשב. יש לקרוא לה לאחר שימוש בפונ' firstMove( ) . move( ) פועלת ע"י קריאה לפונ' החזרת מהלך המתאימה לפי
סוג השחקן.
פונ' זו נקראת ע"י gameLoop( ) לצורך ביצוע מהלך של מחשב.
int firstMove(
)
פונקציה לביצוע צעד ראשון במשחק של שחקן
מחשב. בחירת הצעד נעשית לפי המשקולות של השחקן.
פונ' זו נקראת ע"י gameLoop( ) לצורך ביצוע מהלך ראשון של
מחשב.
int learnPlay(
)
פונקציה להחזרת מהלך של שחקן לומד. זה
הוא מימוש של Gradient Discent
Q-Learning.
int play( )
פונקציה להחזרת מהלך של שחקן לא לומד
המוחר מהלך כמו שחקן לומד (לפי הפונ' הלינארית בattributes), ללא אפשרות לבחירת מהלך
רנדומלי בהסתברות (לא ε-gready).
int playMinMax(
)
כמו play( ) אבל משתמש באלגוריתם αβMinMax לבחירת הצעד.
int randomPlay(
)
מחזירה מהלך חוקי רנדומלי.
double maxQa( )
מחשבת עבור המצב הנוכחי s לכל
מהלך a חוקי בלוח את הקירוב הלינארי של Q(s, a) (המכפלה הסקלארית של וקטור
המשקולות בוקטור ערכי התכונות) ומחזירה את המהלך (העמודה) הממקסם את החישוב הזה
ואת תוצאת החישוב המקסימלית (מתאימה למהלך המוחזר).
חישוב ערך תכונה נעשה ע"י קריאה
לפונ' calculate( ) של מחלקת התכונה המתאימה.
double minmax( )
מחזירה את המהלך המקסימלי וערכו לפי עץ
ה MinMax המתאים ללוח כשחישוב ערך מצב הוא כמו ב maxQa( ). המעבר על עץ הMinMax נעשה לפי אלגוריתם alpha beta
pruning.
חישוב ערך תכונה נעשה ע"י קריאה
לפונ' calculate( ) של מחלקת התכונה המתאימה.
virtual double calculate( )
(במחלקה Attribute)
מחזירה את ערך התכונה המבוקשת (לפי
המחלקה) בלוח המשחק. ערך התכונה תלוי בלוח המשחק וצבע השחקן (אדום – ראשון, שחור –
שני). פונ' זו חייבת במימוש בכל מחלקה שיורשת מ Attribute.
מבני
נתונים חשובים:
ייצוג המשחק: בכדי לייצג מצב במשחק בשלמותו אנו משתמשים בשלשה
< int
board[ROWS][COLS] , int turnOfPlayer
, int gameOn > .
board הוא מטריצה המהווה את לוח
המשחק, כאשר המספור הוא כמו במטריצה רגילה, כלומר הפינה השמאלית עליונה של הלוח
היא board[0][0]
.turnOfPlayer שומר את השחקן שתורו לבצע
מהלך, ו gameOn שומר את מספר המשבצות הריקות
או אפס אם המשחק הסתיים. למעשה board ו turnOfPlayer מספיקים בכדי לייצג מצב במשחק
אבל משיקולי יעילות ונוחות בחרנו להשתמש גם ב gameOn.
המחלקה CompPlayer: מייצגת שחקן מחשב. מכילה
את וקטור המשקולות, וקטור התכונות, וקטור ערכי התכונות של המהלך האחרון, קבועי
הלמידה, הצד של השחקן (אדום או שחור), וסוג השחקן. הוקטורים ממומשים ע"י
מערכים המוקצים דינאמית ומשתנה weightsLength שמחזיק את אורך הוקטורים.
המחלקה prefDialog: מכילה את כל הנתונים
שנמצאים בדיאלוג תחת menu -> Options -> preferences.
משתנים שימושיים נוספים: (מוגדרים ב ConnectFour.cpp) int players[2] מחזיק את סוגי השחקנים, CompPlayer *comps[2] מחזיק את שחקני המחשב (אם יש כאלו).
הקבצים:
ConnectFour.cpp : מכיל את ה-WinMain( )
ומימוש ה GUI
(חוץ מציור הלוח). בנוסף מוגדרים בקובץ מבני נתונים שמייצגים את
המשחק ומשתני עזר גלובליים נוספים.
gameFuncs.h/cpp : מכיל את paintBoard( ) , gameLoop( ) , winStatus( )
ופונקציות נוספות לניהול המשחק. בקובץ header מוגדרים קבועים של ייצוג
השחקנים בלוח, שחקן אנושי ומחשב ועוד.
print.h/cpp : כאן מוגדרות 3
פונקציות עזר להדפסה בחלון הטקסט שמתחת ללוח המשחק.
void printMe(const
char *str)
void printMeNum(int num)
void printClear( )
prefDialog.h/cpp : מחלקה שמייצגת את
דיאלוג preferences .
CompPlayer.h/cpp : המחלקה שמייצגת שחקן
מחשב. פונקציות (מוסברות בקוד) :
Private:
double maxQa(double
&max, int &max_i, int board[][COLS])
double minmax(int depth, double alpha, double beta, int &move, int board[][COLS], int player)
int win(int player, int x, int y, int board[][COLS])
Public:
int firstMove(int board[][COLS])
int learnPlay(int board[][COLS], int r)
int play(int board[][COLS])
int playMinMax(int board[][COLS])
int randomPlay(int board[ROWS][COLS], int r)
void saveToFile(const char *filename)
int move(int board[][COLS], int r)
Attribute.h/cpp והמחלקות היורשות: ממשות תכונות. חישוב התכונה ע"י הפונקציה calculate( ) :
double calculate(const int
board[6][7],const int player)
במחלקה Attribute מוגדרת גם הפונקציה :
bool check4RCDD(int i, int
j, char checkWhat, const int
board[6][7], const int player)
הפונקציה הזו בודקת האם מקום ריק (i,
j) הוא איום,
כלומר אם ימולא ב player יצור ארבע בעמודה, שורה או אלכסון בהתאמה ל checkWhat.