תקשורת ללא "צד-שרת"
בעולם האמיתי כמעט תמיד תקשורת בין משתמשים ובין מסד הנתונים עובדת במודל Client-Server.
אבל אנחנו לא בעולם האמיתי, אבטחה ויעילות כרגע לא חשובות לנו, ואין לנו מחשב שישמש צד שרת. עלינו להגיש פרויקט תוך חודש.
אסביר כאן על האופן שבו כל שחקן (בפרויקט שלי, שבו שני שחקנים משחקים אחד נגד השני) לוחץ על כפתור "מצא לי שחקן יריב" ומתחיל לשחק באם נמצא יריב.
לצורך כך ניצור בבסיס הנתונים טבלה בשם "PlayersWaitingForGame":
ה-ID יהיה כמובן PK ו-Identity.
ה-PlayerID מקושר בקשר גומלין לטבלת Players.
ה-SecondPlayerID מקושר גם הוא בקשר גומלין לטבלת Players.
הרעיון:
שני אנשים שמחפשים יריב צריכים למצוא אחד את השני.
1. שחקן_א (PlayerID=34):
בודק ומגלה שבטבלת PlayersWaitingForGame אין אף שחקן שמחכה שישחקו איתו.
(או שהטבלה ריקה או שהיא לא ריקה אבל כל הרשומות בה הן InConnectionAttempt=1, על כך נסביר בהמשך).
מאחר ואין לו יריב זמין הוא מוסיף את עצמו לטבלה, וממתין (בודק כל פרק זמן קצר) שמישהו יענה לבקשה שלו.
כך נראית כעת הטבלה:
בודק ומגלה שבטבלת PlayersWaitingForGame יש שחקן בעל PlayerID=34 שמחכה שישחקו איתו.
הוא מוסיף את עצמו בתור יריב פוטנציאלי בעמודת SecondPlayerID ומסמן שהמצב כרגע הוא "InConnectionAttempt" כדי שאף אחד לא יפריע לשניהם.
השחקן ממתין שהיריב יבחין בנעשה.
כך נראית כעת הטבלה:
בודק ומגלה שבטבלת PlayersWaitingForGame שחקן PlayerID=71 נענה לבקשה שלו .
מאחר ושני הצדדים למשחק יודעים כעת את ה-ID של היריב שלהם, הוא מוחק את הרשומה מהטבלה.
כך נראית כעת הטבלה:
בודק ומגלה שבטבלת PlayersWaitingForGame הרשומה שפתח שחקן_א נמחקה.
הנה מה טוב ומה נעים הוא מתחיל לשחק עם יריבו!
וכך שני שחקנים מצאו יריב למשחק: הם לחצו על "מצא לי שחקן יריב", השיגו ID של שחקן יריב ואיתו הם יכולים להתחיל לשחק.
הקוד שדואג לארבעת השלבים:
עבור שלבים 1 ו2: יצירת פרוצדורה.
הפרוצדורה מקבלת את ה-PlayerID שלנו.
אם אנחנו "שחקן_א" (משום שבטבלת PlayersWaitingForGame אין אף שחקן שמחכה שישחקו איתו. או שהטבלה ריקה או שהיא לא ריקה אבל כל הרשומות בה הן InConnectionAttempt=1):
היא מוסיפה את ה-PlayerID שלנו בתור רשומה חדשה לטבלה.
במקרה כזה היא מחזירה 1-.
אם אנחנו "שחקן_ב" (משום שבטבלת PlayersWaitingForGame יש שחקן ממתין):
היא מוסיפה את ה-PlayerID בתור יריב פוטנציאלי בעמודת SecondPlayerID ומסמנת שהמצב כרגע הוא InConnectionAttempt=1 כדי שאף אחד לא יפריע לשניהם.
במקרה כזה היא מחזירה את ה-PlayerID של השחקן היריב.
CREATE procedure [dbo].[CheckConnection]
@AskerPlayerID int -- parameter from java
AS
declare @MaxWaitID int -- The ID of the player waiting the longest
declare @ZeroCount int
declare @Count int
declare @tmp int
-- Check if @AskerPlayerID is already in the table because of a stupid programmer:
set @Count = (select count(*) from PlayersWaitingForGame
where [PlayerID] = @AskerPlayerID)
if (@Count > 0)
begin
select -2 as ReturnID -- a stupid programmer
RETURN
end
set @Count = 0
set @Count = (select count(*) from PlayersWaitingForGame
where [SecondPlayerID] = @AskerPlayerID)
if (@Count > 0)
begin
select -3 as ReturnID -- a stupid programmer
RETURN
end
-- Check whether there are available players waiting
set @ZeroCount = (select count(*) from PlayersWaitingForGame
where InConnectionAttempt = 0)
-- If no players are available
-- insert the asker to the table with connection attempt 'Zero'
if (@ZeroCount = 0)
begin
select -1 as ReturnID -- there is no one to connect
insert into PlayersWaitingForGame
values(@AskerPlayerID, 0, null)
RETURN
end
-- If there are players available
else
begin
set @MaxWaitID = (select min(ID)
from PlayersWaitingForGame
where InConnectionAttempt = 0)
-- select @MaxWaitID as ReturnID
select PlayerID as ReturnID
from PlayersWaitingForGame
where ID = @MaxWaitID
update PlayersWaitingForGame
set InConnectionAttempt = 1
,SecondPlayerID = @AskerPlayerID
where ID = @MaxWaitID
end
GO
כדי לקרוא לפרוצדורה מהגאווה, נשתמש בפונקציה "searchAnotherUserWaitingForGame":
// פונקציה שמחפשת בבסיס הנתונים שחקן יריב
// אם היא מצאה היא מחזירה את האיידי שלו
// אם היא לא מצאה היא שמה בבסיס הנתונים את האיידי שהיא קיבלה ששייך לשחקן שלנו ומחזירה מינוס אחד
// אם יש שגיאה היא מחזירה את השגיאה על ידיהפרמטר שהיא קיבלה ומחזירה מינוס שתיים
public int searchAnotherUserWaitingForGame(CustomError CE, int MyPlayerID)
{
Connection con = null;
Statement st = null;
ResultSet rs = null;
String selectString;
int pID = -2;
// בדיקה שהרשימה שקיבלנו מאותחלת
if(CE == null){
CE.CustomMessage = "Error in 'searchUserWaitingForGame': CE = null";
CE.isThereError = true;
return -2;
}
// הפרוצדורה הזו מחזירה את האיידי של מי שיכול לשחק נגדנו
// אם אין אף אחד היא מוסיפה את הבקשה שלנו אל הטבלה כבקשה חדשה שמישהו צריך למצוא
selectString
= "EXEC [dbo].[CheckConnection] " + MyPlayerID + " ";
try
{
con = getConnection();
st = con.createStatement();
rs = st.executeQuery(selectString);
if(rs.next())
{
pID = rs.getInt("ReturnID");
}
else{
CE.CustomMessage = "Error in 'searchUserWaitingForGame': rs.next() = false";
CE.isThereError = true;
return -2;
}
}
catch(Exception e)
{
CE.CustomMessage = "Error in 'searchUserWaitingForGame'";
CE.SystemMessage = e.getMessage();
CE.isThereError = true;
}
finally { // צריך לעשות סגירה של החיבור גם אם נפלנו באקספטשן
closeStatementAndConnection(con, st);
}
return pID;
}
אם קיבלנו 1- הרי שאנחנו "שחקן_א" ועלינו לבצע את שלב 3:
עלינו לבדוק שוב ושוב את הטבלה עד שמישהו יענה לבקשה שלנו.
לשם כך נקרא שוב ושוב לפונקציה "seeIfSomeoneFoundMe":
(בדיליי קצר בין כל קריאה כדי לא להעמיס על בסיס הנתונים)
// הפונקציה בודקת האם כבר מישהו מצא את השחקן בטבלה ורוצה לשחק איתו
// אם כן הפונקציה תחזיר את האיידי שלו
// אם לא הפונקציה תחזיר מינוס אחד
// אם הייתה שגיאה הפונקציה תחזיר מינוס שתיים
// בנוסף הפונקציה מוחקת את השחקן מהטבלה באם התגלה שמישהו מצא אותו
public int seeIfSomeoneFoundMe(CustomError CE, int MyPlayerID){
Connection con = null;
Statement st = null;
ResultSet rs = null;
String selectString;
int pID = -2;
if(CE == null){
CE.CustomMessage = "Error in 'SeeIfSomeoneFoundMe': CE = null";
CE.isThereError = true;
return -2;
}
selectString
= " SELECT [SecondPlayerID] "
+ " FROM [dbo].[" + TableName + "] "
+ " WHERE [InConnectionAttempt] = 1 AND [PlayerID] = " + MyPlayerID + " ";
try
{
con = getConnection();
st = con.createStatement();
rs = st.executeQuery(selectString);
if(rs.next()) // אם יש אדם שמצא אותנו
{
pID = rs.getInt("SecondPlayerID");
}
else{
pID = -1;
}
//// מכאן הפונקציה מוחקת אותי מהטבלה מאחר והתגלה שמצאו אותי
if(pID > -1){ // אם שחקן כלשהו מצא אותי
selectString
= " DELETE FROM [dbo].[" + TableName + "] "
+ " WHERE [PlayerID] = " + MyPlayerID + " ";
int affectedRows = st.executeUpdate(selectString);
if(affectedRows < 1){
CE.CustomMessage = "Error in 'SeeIfSomeoneFoundMe': affectedRows < 1";
CE.isThereError = true;
}
}
}
catch(Exception e)
{
CE.CustomMessage = "Error in 'SeeIfSomeoneFoundMe'";
CE.SystemMessage = e.getMessage();
CE.isThereError = true;
}
finally { // צריך לעשות סגירה של החיבור גם אם נפלנו באקספטשן
closeStatementAndConnection(con, st);
}
return pID;
}
עלינו לבדוק שוב ושוב את הטבלה עד שהיריב יבחין בנו וימחק את הרשומה.
לשם כך נקרא שוב ושוב (בדיליי) לפונקציה "seeIfOpponentNotice":
// בדיקה האם היריב הבחין שמצאתי אותו
// אם הוא הבחין הוא היה אמור למחוק את השורה שלו
// לכן פשוט נבדוק האם הוא מחק את השורה מהטבלה
public boolean seeIfOpponentNotice(CustomError CE, int MyPlayerID){
Connection con = null;
Statement st = null;
ResultSet rs = null;
String selectString;
int pID = -2;
// בדיקה שהרשימה שקיבלנו מאותחלת
if(CE == null){
CE.CustomMessage = "Error in 'SeeIfOpponentNotice': CE = null";
CE.isThereError = true;
return false;
}
selectString
= " SELECT [SecondPlayerID] "
+ " FROM [dbo].[" + TableName + "] "
+ " WHERE [SecondPlayerID] = " + MyPlayerID +" ";
try
{
con = getConnection();
st = con.createStatement();
rs = st.executeQuery(selectString);
if(rs.next()) // אם יש רשומה שהוחזרה הרי שהיריב לא מצא אותנו ולא מחק עדיין את הרשומה
{
return false;
}
else{ // אם היריב הבחין בנו ומחק את השורה מהטבלה כמו שהוא אמור
return true;
}
}
catch(Exception e)
{
CE.CustomMessage = "Error in 'SeeIfSomeoneFoundMe'";
CE.SystemMessage = e.getMessage();
CE.isThereError = true;
}
finally {
closeStatementAndConnection(con, st);
}
return false; // בעיקרון לעולם לא אמורים להגיע לשורה הזאת
}
זהו. עד כאן מימוש ארבעת השלבים, כעת צריך לנהל את מהלך המשחק בין שני היריבים.
יצירת דיליי בג'אווה:
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) { }
עוד חודש...
אין תגובות:
הוסף רשומת תגובה