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.