30 септември 2008

Бъг Ми

Както споменах снощи, днес ще разгледаме гадния бъг от играта ми на морски шах(сорс и обяснения тук). Ако не разбирате Python няма да ви е лесно, за щастие имам няколко много зле написани блог поста, които обясняват по прост и тъп начин нещата.

Започваме от самото начало, играта представлява един списък от 3 други списъка, които представляват редовете на матрицата, в която се съхраняват позициите. ето как изглежда:

matrix=[[[], [], []], [[], [], []], [[], [], []]]


С функцията printmatrix() принтираме на екрана матрицата както трябва:


[[], [], []]
[[], [], []]
[[], [], []]


Това е полето. Следващата стъпка е да се създаде списък със неговите елементи(редове, колони, диагонали):

#първия списък от матрицата е и първия ред
#съответно същото се отнася и за другите редове
row1=matrix[0]
row2=matrix[1]
row3=matrix[2]

#съответните колони са съответните
#редове от списъците, първите елементи
#cа от първата колона, и т.н.
col1=[x[0] for x in matrix]
col2=[x[1] for x in matrix]
col3=[x[2] for x in matrix]

#диагоналите са ясни, надявам се.
diag1=[matrix[0][0],matrix[1][1],matrix[2][2]]
diag2=[matrix[2][0],matrix[1][1],matrix[0][2]]


и самия списък rcd(съкратено от редове, колони и диагонали):

rcd=(row1,row2,row3,col1,col2,col3,diag1,diag2)


Имаме функция която ще провери дали в някой от тези елементи има 3 еднакви елемента(X или O), имаме и функция която ще върти ходовете за да определи кой е победител и т.н., няма да се занимавам с цялата програма, основната идея беше да имам списък от елементите, и за да видим дали след някой от ходовете, нямаме печеливша тройка. Оказа се че програмата не работи както трябва, ако направиш печеливша 3-ка по диагонал или колона, не печелиш. първия ми заподозрян беше функцията good(), предположих че може би тя не си върши работата правилно. След блъскане на главата ми с нея, си легнах, без решение. След като спах около 1 час, се събудих, сетил се за причината. Все още нямах решение, просто станах, записах причината на едно листче, и си легнах. На листа е записано "списъкът се променя, но колоните и диагоналите-не". Сетих се къде е проблема, списъкът е съставен от списък със списъци. Списъците могат да се променят, това е цялата идея, имам един глобален списък, който променям, след това инспектирам, но колоните и диагоналите са указатели към първоначалните елементи([]), които по късно се заменят със X или O, докато редовете са указатели към целите списъци от списъци([[],[],[]]), което означава че row1,2 и 3 се променят по очакванията, но col1,2,3 и diag1 и 2, не се променят както трябва. Първото решение беше да се сложи всичко това във функция която да рефрешва съдържанието на всички елементи, това е и прословутия хак, около който се върти всичко:


def refresh():
row1=matrix[0]
row2=matrix[1]
row3=matrix[2]
col1=[x[0] for x in matrix]
col2=[x[1] for x in matrix]
col3=[x[2] for x in matrix]
diag1=[matrix[i][i] for i in xrange(3)]
diag2=[matrix[i][m] for (i,m) in zip((2,1,0),xrange(3))]
#най- грозния ред:
global rcd
rcd=(col1,col2,col3,row1,row2,row3,diag1,diag2)


Забелязвате че просто на променливите им ъпдейтваме съдържанието, имаме една глобална променлина rcd, която приема нужните стойности, и след това с good() можем да я проверим за съвпадения(в тази версия на сорса, вече се казва same()). Този код работи, втората версия от горния линк е работеща игра на морски шах, работи и няма този бъг. Всъщност бъгът си е там, но просто сме го заоликолили. Истинския проблем е че зависех от един куп глобални променливи, които се променят постоянно и трябва да им следя състоянието. Това доведе до проблеми, и след като ги пооправих, да не си личи кафявото по гащите ми, реших да преработя програмата използвайки идеите на функционалното програмиране. Резултатът е пет функции, и сериозно опростяване, виждате в първия линк колко прост е кода в 3-тата версия, по къс е и няма глобални променливи, спестих и локални променливи на местата където можех, също така се опитах да изолирам до някъде страничните ефекти(присвояване, принтиране), но не се увлякох особено много, можех да напиша програмата в още по функционален стил, можеше и да я напиша на scheme(може да пробвам), можех да се гавря с езика, както тук са направили колегите. Но разбира се аз не съм от тея които бъркат пръднята с аналния оргазъм, за това реших да не насилвам python да го правя на lisp.

Това беше един сравнително приличен пример за използването на полезни функционални идеи, във един обектно ориентиран език(ооп понякога е просто процедурно програмиране, със фънки синтаксис).

Няма коментари: