Rem Project: Noughts and Crosses Rem Created: 12/11/2006 10:25:57 Rem ***** Main Source File ***** set display mode 1024,768,32 color backdrop 0 autocam off sync on `constants global sw# global sh# sw#=screen width() sh#=screen height() global highestentitynumber global menuseed menuseed=1000 global highestmenuentitynumber segments=3 computerturn=1:chosencomputer=1 playerturn=0::chosenplayer=0 turn=1 level=1 `arrays global dim square(segments,segments) global dim gridlinex(segments) global dim gridliney(segments) global dim nought(segments,segments) global dim cross(segments,segments) global dim crosson(segments,segments) `setup background and board make_grid(segments) make_noughts(segments) make_crosses(segments) gosub setup_plasma gosub make_menu gosub setup_cursor ink -1,0 set text size 20 set text to normal starttime=timer() do `timing routine to keep movement constant time=timer()-starttime dtperloop#=time-oldtime inc totaldt#,dtperloop# oldtime=time inc loopnumber if loopnumber=10 loopnumber=0 dt#=totaldt#/10.0 totaldt#=0 endif `_____________________________________________ `main events if mousedown=1 and mouseclick()=0 then mousedown=0 sprite cursor,mousex(),mousey(),cursor if menuoff=0 then gosub menu_select if menuoff=1 if playerturn=1 then gosub player gosub checkforwinner gosub checkfordraw endif if menuoff=1 if computerturn=1 then gosub computer gosub checkforwinner gosub checkfordraw endif gosub update_plasma sync loop `subroutines make_menu: ink -1,0 set text size 100 set text font "Arial" set text to bold `make title title=free_menu_entity() make_text_button(title,0,0,"TicTacToe") sprite title,screen width()/2.0-sprite width(title)/2.0,screen height()/4.0,title set sprite alpha title,100 logo=free_menu_entity() make_text_button(logo,sprite x(title)-20,sprite y(title)-20,"X") set sprite alpha logo,20 size sprite logo,300,300 `make menu buttons: easy=free_menu_entity() make_text_button(easy,sprite x(title),sprite y(title)+sprite height(title)+20,"Easy") size sprite easy,sprite width(easy)/4.0,sprite height(easy)/4.0 hard=free_menu_entity() make_text_button(hard,sprite x(title),sprite y(easy)+sprite height(easy)+5,"Hard") set sprite alpha hard,50 size sprite hard,sprite width(hard)/4.0,sprite height(hard)/4.0 igofirst=free_menu_entity() make_text_button(igofirst,sprite x(title),sprite y(hard)+sprite height(hard)+20,"Computer goes first") set sprite alpha igofirst,250 size sprite igofirst,sprite width(igofirst)/4.0,sprite height(igofirst)/4.0 yougofirst=free_menu_entity() make_text_button(yougofirst,sprite x(title),sprite y(igofirst)+sprite height(igofirst)+5,"Player goes first") set sprite alpha yougofirst,50 size sprite yougofirst,sprite width(yougofirst)/4.0,sprite height(yougofirst)/4.0 size3x3=free_menu_entity() make_text_button(size3x3,sprite x(title),sprite y(yougofirst)+sprite height(yougofirst)+20,"3 in a row") set sprite alpha size3x3,250 size sprite size3x3,sprite width(size3x3)/4.0,sprite height(size3x3)/4.0 size4x4=free_menu_entity() make_text_button(size4x4,sprite x(title),sprite y(size3x3)+sprite height(size3x3)+5,"4 in a row") set sprite alpha size4x4,50 size sprite size4x4,sprite width(size4x4)/4.0,sprite height(size4x4)/4.0 size5x5=free_menu_entity() make_text_button(size5x5,sprite x(title),sprite y(size4x4)+sprite height(size4x4)+5,"5 in a row") set sprite alpha size5x5,50 size sprite size5x5,sprite width(size5x5)/4.0,sprite height(size5x5)/4.0 play=free_menu_entity() make_text_button(play,sprite x(title)+sprite width(title)/2.0,sprite y(size5x5)+sprite height(size5x5)+30,"Play Game") sprite play,sprite x(play)-sprite width(play)/4.0,sprite y(play),sprite image(play) size sprite play,sprite width(play)/2.0,sprite height(play)/2.0 return menu_select: if mouseclick()=1 and sprite collision(cursor,easy)=1 mousedown=1 set sprite alpha easy,250 set sprite alpha hard,50 level=1 endif if mouseclick()=1 and sprite collision(cursor,hard)=1 mousedown=1 set sprite alpha easy,50 set sprite alpha hard,250 level=2 endif if mouseclick()=1 and sprite collision(cursor,igofirst)=1 mousedown=1 set sprite alpha yougofirst,50 set sprite alpha igofirst,250 playerturn=0:chosenplayer=0 computerturn=1:chosencomputer=1 endif if mouseclick()=1 and sprite collision(cursor,yougofirst)=1 mousedown=1 set sprite alpha yougofirst,250 set sprite alpha igofirst,50 playerturn=1:chosenplayer=1 computerturn=0:chosencomputer=0 endif if mouseclick()=1 and sprite collision(cursor,size3x3)=1 mousedown=1 set sprite alpha size3x3,250 set sprite alpha size4x4,50 set sprite alpha size5x5,50 segments=3 gosub delete_all make_grid(segments) make_noughts(segments) make_crosses(segments) endif if mouseclick()=1 and sprite collision(cursor,size4x4)=1 mousedown=1 set sprite alpha size3x3,50 set sprite alpha size4x4,250 set sprite alpha size5x5,50 segments=4 gosub delete_all make_grid(segments) make_noughts(segments) make_crosses(segments) endif if mouseclick()=1 and sprite collision(cursor,size5x5)=1 mousedown=1 set sprite alpha size3x3,50 set sprite alpha size4x4,50 set sprite alpha size5x5,250 segments=5 gosub delete_all make_grid(segments) make_noughts(segments) make_crosses(segments) endif if mouseclick()=1 and sprite collision(cursor,play)=1 `flag mouse press in order to wait for release mousedown=1 `hide menu items for n=menuseed+1 to highestmenuentitynumber hide sprite n next n `show gridlines for x=1 to segments-1 show sprite gridlinex(x) next x for y=1 to segments-1 show sprite gridliney(y) next y menuoff=1 endif return delete_all: for n=1 to highestentitynumber if sprite exist(n)=1 then delete sprite n if image exist(n)=1 then delete image n next n highestentitynumber=0 `resize arrays to hold new amount of segments undim square(0) undim gridlinex(0) undim gridliney(0) undim nought(0) undim cross(0) undim crosson(0) global dim square(segments,segments) global dim gridlinex(segments) global dim gridliney(segments) global dim nought(segments,segments) global dim cross(segments,segments) global dim crosson(segments,segments) return function make_text_button(entity,x,y,text$) create bitmap 1,text width(text$),text height(text$) text 0,0,text$ blur bitmap 1,4 get image entity,0,0,text width(text$),text height(text$),1 alpha(entity,255,255,255) delete bitmap 1 sprite entity,x,y,entity endfunction update_plasma: roll camera right 0.005*dt# y#=amp#*sin(theta#) theta#=theta#+0.01*dt# set matrix height 1,a,b,y# for x=1+1 to rows-1 for z=1+1 to columns-1 if x<rows distxp1#=get matrix height(1,x+1,z)-get matrix height (1,x,z) endif if x>1 distxm1#=get matrix height(1,x-1,z)-get matrix height (1,x,z) endif if z<columns distzp1#=get matrix height(1,x,z+1)-get matrix height (1,x,z) endif if z>1 distzm1#=get matrix height(1,x,z-1)-get matrix height (1,x,z) endif vectorsumx#=(distxp1#+distxm1#) vectorsumz#=(distzp1#+distzm1#) vectorsum#=vectorsumx#+vectorsumz# a#=vectorsum#*elasticity# v#(obnum(x,z))=v#(obnum(x,z))+a#*dt# if v#(obnum(x,z))>0.01 then v#(obnum(x,z))=0.01 if v#(obnum(x,z))<-0.01 then v#(obnum(x,z))=-0.01 set matrix height 1,x,z,get matrix height(1,x,z)+v#(obnum(x,z))*dt# if get matrix height(1,x,z)>amp# then set matrix height 1,x,z,amp# if get matrix height(1,x,z)<-amp# then set matrix height 1,x,z,-amp# next z next x update matrix 1 return function free_entity() n=0 repeat inc n until sprite exist(n)=0 and image exist(n)=0 and object exist(n)=0 if n>highestentitynumber then highestentitynumber=n endfunction n function free_menu_entity() n=menuseed repeat inc n until sprite exist(n)=0 and image exist(n)=0 and object exist(n)=0 if n>highestmenuentitynumber then highestmenuentitynumber=n endfunction n function make_noughts(segments) ink -1,0 set text size 100 set text font "Arial" set text to bold text$="O" create bitmap 1,text width(text$),text height(text$) text 0,0,text$ nought=free_entity() blur bitmap 1,5 get image nought,0,0,text width(text$),text height(text$),1 alpha(nought,255,255,255) delete bitmap 1 for x=1 to segments for y=1 to segments nought(x,y)=free_entity() sprite nought(x,y),sprite x(square(x,y)),sprite y(square(x,y)),nought set sprite alpha nought(x,y),40 set sprite diffuse nought(x,y),0,0,0 size sprite nought(x,y),sprite width(square(x,y)),sprite height(square(x,y)) hide sprite nought(x,y) next y next x endfunction function make_crosses(segments) ink -1,0 set text size 100 set text font "Arial" set text to bold text$="X" create bitmap 1,text width(text$),text height(text$) text 0,0,text$ crosses=free_entity() blur bitmap 1,5 get image crosses,0,0,text width(text$),text height(text$),1 alpha(crosses,255,255,255) delete bitmap 1 for x=1 to segments for y=1 to segments cross(x,y)=free_entity() sprite cross(x,y),sprite x(square(x,y)),sprite y(square(x,y)),crosses set sprite alpha cross(x,y),40 set sprite diffuse cross(x,y),0,0,0 size sprite cross(x,y),sprite width(square(x,y)),sprite height(square(x,y)) hide sprite cross(x,y) next y next x endfunction function make_grid(segments) create bitmap 1,10,10 black=free_entity() get image black,0,0,1,1 ink rgb(0,0,255),0 box 0,0,10,10,rgb(0,0,255),0,rgb(255,255,255),-1 blue=free_entity() get image blue,0,0,10,10,1 alpha(blue,255,255,255) delete bitmap 1 gridstartx#=sw#*0.25 gridend#=sw#*0.75 gridwidth#=gridend#-gridstartx# gridstarty#=sh#/2.0-gridwidth#/2.0 linewidth#=5 squaresize#=(gridwidth#/segments)-(linewidth#*(segments-1))/segments `squares for x=1 to segments for y=1 to segments square(x,y)=free_entity() sprite square(x,y),gridstartx#+((squaresize#+linewidth#)*(x-1)),gridstarty#+((squaresize#+linewidth#)*(y-1)),black size sprite square(x,y),squaresize#,squaresize# next y next x `lines for x=1 to segments-1 gridlinex(x)=free_entity() sprite gridlinex(x),sprite x(square(x,1))+squaresize#,gridstarty#,blue set sprite alpha gridlinex(x),40 size sprite gridlinex(x),linewidth#,gridwidth# hide sprite gridlinex(x) next x for y=1 to segments-1 gridliney(y)=free_entity() sprite gridliney(y),gridstartx#,sprite y(square(1,y))+squaresize#,blue set sprite alpha gridliney(y),40 size sprite gridliney(y),gridwidth#,linewidth# hide sprite gridliney(y) next y endfunction player: for x=1 to segments for y=1 to segments if sprite collision(cursor,square(x,y))>0 and mousedown=0 and mouseclick()>0 and sprite visible(cross(x,y))=0 and sprite visible(nought(x,y))=0 show sprite nought(x,y) `determine whether player played an edge or a corner if x=1 and y=segments or x=segments and y=1 or x=1 and y=1 or x=segments and y=segments playeredge=0 else playeredge=1 endif playerturn=0 computerturn=1 inc turn endif next y next x return computer: `level 1 - (easy), computer moves are random if level=1 if squareselected=0 x=rnd(segments-1)+1 y=rnd(segments-1)+1 if sprite visible(cross(x,y))=0 and sprite visible(nought(x,y))=0 selectedx=x selectedy=y waitabit=30 squareselected=1 endif endif if waitabit>0 dec waitabit endif endif `level 2 (hard) - computer uses some AI if level=2 `check ahead one move by testing all possible spaces and giving a rating for each space bestrating=0 rating=0 bestopprating=0 blockneeded=0 for x=1 to segments for y=1 to segments `test possibilities at x,y `check square is free if sprite visible(cross(x,y))=0 and sprite visible(nought(x,y))=0 `give a rating of 1 if the square is available rating=1 `assign this square temporarily as a fail safe, incase a better one isn't found tempx=x tempy=y `create a 'marker' for this squaresquare crosson(x,y)=1 `check for strength of following moves `check row rowscore=0 `the computers score for this row opprowscore=0 `the oppositions score for this row void=0 for checkx=1 to segments if crosson(checkx,y)=1 or sprite visible(cross(checkx,y))=1 then inc rowscore if sprite visible(nought(checkx,y))=1 then inc opprowscore:void=1 next checkx if void=1 then rowscore=0 if rowscore>rating then rating=rowscore if opprowscore>bestopprating then bestopprating=opprowscore `check column columnscore=0 oppcolumnscore=0 void=0 for checky=1 to segments if crosson(x,checky)=1 or sprite visible(cross(x,checky))=1 then inc columnscore if sprite visible(nought(x,checky))=1 then inc oppcolumnscore:void=1 `if a nought exists in this column them the score will be void next checky if void=1 then columnscore=0 if columnscore>rating then rating=columnscore if oppcolumnscore>bestopprating then bestopprating=oppcolumnscore `check diagonals `top left to bottom right diagonalscore1=0 oppdiagonalscore1=0 void=0 if x=y for xy=1 to segments if crosson(xy,xy)=1 or sprite visible(cross(xy,xy))=1 then inc diagonalscore1 if sprite visible(nought(xy,xy))=1 then inc oppdiagonalscore1:void=1 next xy if void=1 then diagonalscore1=0 if diagonalscore1>rating then rating=diagonalscore1 if oppdiagonalscore1>bestopprating then bestopprating=oppdiagonalscore1 endif `top right to bottom left diagonalscore2=0 oppdiagonalscore2=0 void=0 if x=segments-y+1 checky=segments for checkx=1 to segments if crosson(checkx,checky)=1 or sprite visible(cross(checkx,checky))=1 then inc diagonalscore2 if sprite visible(nought(checkx,checky))=1 then inc oppdiagonalscore2:void=1 dec checky next checkx if void=1 then diagonalscore2=0 if diagonalscore2>rating then rating=diagonalscore2 if oppdiagonalscore2>bestopprating then bestopprating=oppdiagonalscore2 endif `remove marker crosson(x,y)=0 `check for winning moves if rowscore=segments then selectedx=x:selectedy=y:squareselected=1:goto stopchecking if columnscore=segments then selectedx=x:selectedy=y:squareselected=1:goto stopchecking if diagonalscore1=segments then selectedx=x:selectedy=y:squareselected=1:goto stopchecking if diagonalscore2=segments then selectedx=x:selectedy=y:squareselected=1:goto stopchecking `check to see if a block is necessary if bestopprating=segments-1 then blockneeded=1:bestopprating=0:selectedx=x:selectedy=y:squareselected=1 if blockneeded=0 `attack rating: `if a move gives advantage in more than one direction then increase the rating by two if rowscore>1 and columnscore>1 then inc rating,2 if rowscore>1 and diagonalscore1>1 then inc rating,2 if rowscore>1 and diagonalscore2>1 then inc rating,2 if columnscore>1 and diagonalscore1>1 then inc rating,2 if columnscore>1 and diagonalscore2>1 then inc rating,2 if diagonalscore1>1 and diagonalscore2>1 then inc rating,2 `defensive rating: `if a move blocks in more than one direction then increase the rating of this move by one if opprowscore>0 and oppcolumnscore>0 then inc rating if opprowscore>0 and oppdiagonalscore1>0 then inc rating if opprowscore>0 and oppdiagonalscore2>0 then inc rating if oppcolumnscore>0 and oppdiagonalscore1>0 then inc rating if oppcolumnscore>0 and oppdiagonalscore2>0 then inc rating if oppdiagonalscore1>0 and oppdiagonalscore2>0 then inc rating `Set Plays ************************************************** `these are preprogrammed opening strategies, not AI `if it is turn two, then anywhere around the edges or corners is not a good move: if turn=2 if x=1 or y=1 or x=segments or y=segments rating=0 endif endif `if it is turn 3, a corner is best if turn=3 if x=1 and y=segments or x=segments and y=1 or x=1 and y=1 or x=segments and y=segments inc rating else rating=0 endif endif `if it is turn 4, an edge is the best response to a corner, and vice versa, unless player has the centre, in which case a corner is better if turn=4 if x=1 and y=segments or x=segments and y=1 or x=1 and y=1 or x=segments and y=segments if playeredge=0 and sprite visible(nought(2,2))=0 rating=0 else inc rating endif endif endif `check if the rating for this choice of square is the best choice so far if rating>bestrating bestrating=rating squareselected=1 selectedx=x selectedy=y endif endif endif next y next x `if no good squares are found, use the temporary one assigned at the beginning of this routine if squareselected=0 selectedx=tempx selectedy=tempy squareselected=1 endif endif stopchecking: `once a square is selected, show the cross in the square if squareselected and waitabit=0 show sprite cross(selectedx,selectedy) inc turn playerturn=1 computerturn=0 squareselected=0 endif return checkforwinner: `check noughts `check rows for y=1 to segments win=0 for x=1 to segments if sprite visible(nought(x,y))=1 then inc win next x if win=segments gosub noughtswintext gosub restart goto getout endif next y `check columns for x=1 to segments win=0 for y=1 to segments if sprite visible(nought(x,y))=1 then inc win next x if win=segments gosub noughtswintext gosub restart goto getout endif next x `check diagonals `top left to bottom right win=0 for xy=1 to segments if sprite visible(nought(xy,xy))=1 then inc win next xy if win=segments gosub noughtswintext gosub restart goto getout endif `top right to bottom left y=segments win=0 for x=1 to segments if sprite visible(nought(x,y))=1 then inc win dec y next x if win=segments gosub noughtswintext gosub restart goto getout endif `check crosses `check rows for y=1 to segments win=0 for x=1 to segments if sprite visible(cross(x,y))=1 then inc win next x if win=segments gosub crosseswintext gosub restart goto getout endif next y `check columns for x=1 to segments win=0 for y=1 to segments if sprite visible(cross(x,y))=1 then inc win next x if win=segments gosub crosseswintext gosub restart goto getout endif next x `check diagonals `top left to bottom right win=0 for xy=1 to segments if sprite visible(cross(xy,xy))=1 then inc win next xy if win=segments gosub crosseswintext gosub restart goto getout endif `top right to bottom left y=segments win=0 for x=1 to segments if sprite visible(cross(x,y))=1 then inc win dec y next x if win=segments gosub crosseswintext gosub restart goto getout endif getout: return crosseswintext: ink -1,0 set text size 20 set text to normal text$="I win! Press any key." text screen width()/2.0-text width(text$)/2.0,screen height()/2.0,text$ sync wait key return noughtswintext: ink -1,0 set text size 20 set text to normal text$="You win! Press any key." text screen width()/2.0-text width(text$)/2.0,screen height()/2.0,text$ sync wait key return checkfordraw: if turn>segments*segments ink -1,0 set text size 20 set text to normal text$="Draw. Press any key." text screen width()/2.0-text width(text$)/2.0,screen height()/2.0,text$ sync wait key gosub restart endif return restart: playerturn=chosenplayer computerturn=chosencomputer turn=1 for n=1 to highestentitynumber if sprite exist(n)=1 then hide sprite n next n for n=menuseed to highestmenuentitynumber if sprite exist(n) show sprite n endif next n menuoff=0 return function alpha(image,red,green,blue) `this is for converting an image with no alpha layer into a monochrome image with alpha layer created from rgb values. `image: image number `red, gree, blue: the single colour the final image will be memblock=1 make memblock from image memblock,image sx=memblock dword(memblock,0) sy=memblock dword(memblock,4) for x=0 to sx-1 for y=0 to sy-1 pos=(y*sx+x)*4+12 b=memblock byte(memblock,pos) g=memblock byte(memblock,pos+1) r=memblock byte(memblock,pos+2) a=int((b+g+r)/3.0) b=blue g=green r=red if a>255 then a=255 if a<0 then a=0 write memblock byte memblock,pos,b write memblock byte memblock,pos+1,g write memblock byte memblock,pos+2,r write memblock byte memblock,pos+3,a next y next x make image from memblock image,memblock endfunction setup_plasma: amp#=20.0 rows=100 columns=100 elasticity#=0.0002 a=20 b=5 position camera rows/2,50,columns/2 yrotate camera -90 point camera rows/2,0,columns/2 plasma=2000 create bitmap 1,10,10 box 0,0,10,10,rgb(0,255,0),rgb(255,0,0),rgb(0,0,255),rgb(255,255,0) get image plasma,0,0,10,10 delete bitmap 1 num=rows*columns dim obnum(rows+1,columns+1) dim v#(rows,columns) make matrix 1,rows,columns,rows,columns for x=1 to rows for z=1 to columns obnum(x,z)=ob ob=ob+1 next z next x prepare matrix texture 1,plasma,rows,columns tile=1 for x=rows-1 to 0 step -1 for z=0 to columns-1 set matrix tile 1,z,x,tile inc tile next z next x return setup_cursor: cursor=10000 create bitmap 1,1,1 get image cursor,0,0,1,1 delete bitmap 1 sprite cursor,0,0,cursor return