//******************************** //* REVERSI PRIME v1.11 * //* by Piotr Kowalewski (komame) * //* February-May 2025 * //******************************** #pragma mode( separator(.,;) integer(h32) ) //------------------------------------------------------------------------------ // DrawIcon() // Draws an icon at the specified coordinate in the given graphic buffer 'g'. // If 'clear' is true, it first clears the area using the board background from G5. // Also handles fading effects when the frame value is greater than 99. //------------------------------------------------------------------------------ DrawIcon(g,coord,frame,clear) Begin If clear Then Blit_P(g,coord,G5,coord,coord+27); End; If frame>99 Then Local alpha=256-IP(frame / 100)*32; frame:=frame Mod 100; Blit_P(g,coord,G4,(frame-1)*27,0,frame*27,27,#FF000000,alpha); Else Blit_P(g,coord,G4,(frame-1)*27,0,frame*27,27); End; End; //------------------------------------------------------------------------------ // DrawField() // Draws a 3D-like rectangle in the given graphic buffer. //------------------------------------------------------------------------------ DrawField(g,x,y,width,height) Begin Rect_P(g,x,y,x+width+1,y+height+1,#555555,#FF000000);//#AAA9AA); Line_P(g,x,y,x+width,y,#D9D9D9); Line_P(g,x,y,x,y+height,#F9F9F9); End; //------------------------------------------------------------------------------ // DrawScore() // Renders the current score for a given player by compositing score graphics // from various buffers. //------------------------------------------------------------------------------ DrawScore(player,value) Begin Local score:=Right(0+Strig(value,1),2); Local x1=(score[1]-47)*22+136,x2=(score[2]-47)*22+136,y=IfTE(player=1,74,192); Blit_P(G7,G5,IfTE(player=1,3,123),69,IfTE(player=1,70,190),69+40); Blit_P(G7,0,0,32,39,G3,x1,4,x1+18,26,#BDAD93); Blit_P(G7,29,0,61,39,G3,x2,4,x2+18,26,#BDAD93); Blit_P(251,y,G7); End; //------------------------------------------------------------------------------ // ResetDiscs() // Resets the disc indicators (pulsating icons) in the scoreboard area. //------------------------------------------------------------------------------ ResetDiscs() Begin DrawIcon(G0,{268,39},5,False); DrawIcon(G0,{268,157},17,False); End; //------------------------------------------------------------------------------ // DrawRoundedFrameG8() // Draws a rounded dialog window. // - G9 is used as a temporary buffer to construct rounded corners. // - The final composite frame is stored in G8. //------------------------------------------------------------------------------ DrawRoundedFrameG8(width,height) Begin Local i,dx,dy,pts = {{5,0},{4,1},{3,1},{2,2}}, bck = {{7,3},{6,4},{5,4}}; DimGrob_P(G8,width,height,#FFFFFFFF); DimGrob_P(G9,width,height,#FFFFFFFF); width:=width-1; height:=height-1; Blit_P(G8,3,3,G5,10,234-height,width-5+10,244,#0);//,#7F); Rect_P(G8,3,3,width-3,26,#DF000000); For i From 1 To Size(pts) Do dx:=pts[i,1]; dy:=pts[i,2]; Rect_P(G9, dx, dy, width-dx, height-dy, #7F7F7F, #FFFFFFFF); Rect_P(G9, dy, dx, width-dy, height-dx, #7F7F7F, #FFFFFFFF); End; For i From 1 To Size(bck) Do dx:=bck[i,1]; dy:=bck[i,2]; Rect_P(G9, dx, dy, width-dx, height-dy, #FFFFFF); Rect_P(G9, dy, dx, width-dy, height-dx, #FFFFFF); End; Blit_P(G8,G9,0,0,width+1,height+1,#FFFFFF); End; //------------------------------------------------------------------------------ // Mono() // Converts the input text to a fixed-width (monospaced) font by transforming // each character into its full-width counterpart. //------------------------------------------------------------------------------ Mono(s) Begin Replace(Char(Asc(s)+65248),Char(65280)," "); End; //------------------------------------------------------------------------------ // FlipCount() // Checks in all eight directions how many opponent discs would be flipped // if the current player places a disc at (x, y). // When 'checkOnly' is true, it returns immediately if there are any discs that can be flipped (value<>0). //------------------------------------------------------------------------------ FlipCount(x, y, board, currPlayer, checkOnly) Begin Local currDisc = IfTE(currPlayer = 1, 1, 21), opponentDisc = IfTE(currPlayer = 1, 21, 1); Local deltaX, deltaY, disc, flipCount = 0; For deltaX From -1 To 1 Do For deltaY From -1 To 1 Do If deltaX = 0 And deltaY = 0 Then Continue; End; Local tempX := x + deltaX, tempY := y + deltaY; Local foundOpponent := False, tempCount := 0; While tempX >= 1 And tempX <= 8 And tempY >= 1 And tempY <= 8 Do disc := board[tempX, tempY]; Case If disc = 0 Then Break; End; If disc = opponentDisc Then foundOpponent := True; tempCount := tempCount + 1; End; If disc = currDisc Then If foundOpponent Then If checkOnly Then Return tempCount; End; flipCount := flipCount + tempCount; End; Break; End; End; tempX := tempX + deltaX; tempY := tempY + deltaY; End; End; End; flipCount; End; //------------------------------------------------------------------------------ // InitTitleGraphics() // Initializes and configures all graphic buffers and resources for the title screen. // Sets dimensions for buffers, draws the title text, and prepares background graphics. //------------------------------------------------------------------------------ InitTitleGraphics() Begin Local Name=Mono("REVERSI0123456789"), i, bg=#BDAD93; local dx, dy, pts = {{5,0},{4,1},{3,1},{2,2}}, bck = {{7,3},{6,4},{5,4}}; DimGrob_P(G1,320,240,0); DimGrob_P(G2,400,120); DimGrob_P(G3,374,26); DimGrob_P(G8,220,115); DimGrob_P(G7,272,74); G5:=AFiles("DiscFrames.png"); DimGrob_P(G4,594,27,#FF000000); Blit_P(G4,G5,0,0,594,27,#BDAD93); G5:=AFiles("Board.png"); Rect_P(G3,156,0,373,25,bg); For i From 0 To 4 Do TextOut_P(Name,G3,i,0,7); End; Blit_P(G7,0,0,272,60,G3,5,2,153,23); // Title graphic composition Rect_P(G7,209,58,267,72,#FF7F7F); // PRIME background Rect_P(G7,207,59,269,71,#FF3F3F); PixOn_P(G3,254,16,bg); PixOn_P(G3,342,10,bg); DrawRoundedFrameG8(220,125); DrawField(G8,22,92,80,25); DrawField(G8,116,92,80,25); TextOut_P("AI GAME SETUP",G8,40,2,6,#3F3F3F); TextOut_P("AI Disc Color:",G8,21,31,5,#3F3F2F); TextOut_P("Starting Player:",G8,21,61,5,#3F3F3F); TextOut_P("Cancel",G8,35,94,5,#0); TextOut_P("Start",G8,139,94,5,#0); End; //------------------------------------------------------------------------------ // InitGameGraphics() // Sets up the gameplay screen: draws the board grid, initializes background layers, // prepares score displays, and configures graphic buffers for gameplay. //------------------------------------------------------------------------------ InitGameGraphics(ai) Begin Local pl, i, y=10; SubGrob(G5,G2); For i From 28 To 231 Step 29 Do Line_P(G2,0,i,232,i,#3F937554); Line_P(G2,i,0,i,232,#2F937554); End; For i From 28 To 231 Step 29 Do Line_P(G2,0,i-28,232,i-28,#4FE5D3BE); Line_P(G2,i-28,0,i-28,232,#2FE5D3BE); End; Rect_P(#0); Triangle_P(G0,243,0,319,0,243,239,#0000FF,#0000FF,#FF0000); Triangle_P(G0,319,0,319,239,243,239,#0000FF,#FF0000,#FF0000); DrawField(G0,247,4,67,112); Blit_P(248,5,248+67,5+112,G5,0,0,67,112); DrawField(G0,247,122,67,112); Blit_P(248,123,248+67,123+112,G5,120,0,187,112); For i From 1 To 2 Do pl:=IfTE(i=ai,"AI","PLAYER"); TextOut_P(pl,BitSR(67-TextOut_P(pl,G1,0,0,4),1)+248,y,4); y:=y+118; End; ResetDiscs(); DimGrob_P(G1,240,240,#0); DimGrob_P(G6,27,27); DimGrob_P(G7,62,40); DrawScore(1,0); DrawScore(2,0); End; //------------------------------------------------------------------------------ // Title() // Displays the title screen with a dynamically animated background and menu buttons. //------------------------------------------------------------------------------ Title() Begin Local t=Ticks, a=0, b=20, y, bc=#E0E0E0, mode, menu=-1; Local mx, my, c=(-40,-40), r=Exp(i*Pi/90), p=(0,-40), iter=0, mc, wt=13; Local ai=2, starting=1, keyDown=1, mouseDown=1, prime=Mono("PRIME"), author=Mono("by Piotr Kowalewski"); InitTitleGraphics(); // Initialize graphic buffers and assets needed for the title screen Repeat // Synchronize animation timing If Ticks0,p-80,p),G2); Blit_P(G1,0,40,G1,0,0); If (iter:=iter+1)>=180 Then // After 180 iterations, adjust the animation parameters c:=c+IFTE(Re(c)<0,80,-80); // Reverse the rotation direction r:=1/r; iter:=0; End; // Change color of the background animation perdiodically If iter Mod 2 Then b:=b+1; a:=a+1; If a>40 Then a:=0; bc:=RGB(214+RandInt(3)*12,214+RandInt(2)*12,186+RandInt(5)*12); Else If b>40 Then b:=0; End; End; End; // Draw the title graphic from buffer G7 with transparency Blit_P(G1,24,4,G7,0,0,272,78,#FFFFFF,112); // Render additional title text elements TextOut_P(prime,G1,233,64,2,#FFFFFF); TextOut_P(author,G1,63,81,1); TextOut_P("v1.10, February-March 2025",G1,182,227,1,#222222); // Read current mouse coordinates mx:=Mouse(0); my:=Mouse(1); If mode=0 Then // Draw menu option backgrounds using filled polygons and outline them mc:=0; For y From 108 To 180 Step 36 Do mc := (mc+1) Mod 3; FillPoly_P(G1,{80,y+15,95,y,225,y,240,y+15,225,y+30,95,y+30},#00007F,IfTE(menu=mc And (wt>5 And wt<8 Or wt>11),#0,#6F)); Line_P(G1,{{80,y+15},{95,y},{225,y},{240,y+15},{225,y+30},{95,y+30}},{{1,2,#6F},{2,3,#6F},{3,4,#6F},{4,5,#6F},{5,6,#6F},{6,1,#6F}},{-1}); End; // Draw menu button texts TextOut_P("Player vs Player",G1,96,112,5,#FFFFFF); TextOut_P("Player vs AI",G1,113,148,5,#FFFFFF); TextOut_P("Exit",G1,145,184,5,#FFFFFF); // Handle mouse input to detect selection on one of the menu options If menu=-1 Then If mouseDown=0 Then mouseDown:=1; If mx>80 And mx<240 Then Case If my>108 And my<138 Then menu:=1; End; If my>144 And my<174 Then menu:=2; End; If my>180 And my<210 Then menu:=0; End; End; End; End; Else wt:=wt-1; If wt=0 Then // Confirm the selected menu option Case If menu=0 Then Return 0; // Exit game End; If menu=1 Then Return 2; // Start Player vs. Player game End; If menu=2 Then mode:=1; // Switch to AI setup mode wt:=13; End; End; End; End; Else // mode=1: AI setup mode (additional UI for AI options) Blit_P(G1,50,95,G8); DrawIcon(G1,{223,125},IfTE(ai=1,2,20),0); DrawIcon(G1,{223,155},IfTE(starting=1,2,20),0); If mouseDown=0 Then mouseDown:=1; If mx>222 And mx<250 Then If my>124 And my<152 Then ai:=IfTE(ai=1,2,1); Else If my>154 And my<182 Then starting:=IfTE(starting=1,2,1); End; End; End; If my>186 And my<214 Then // Detect Cancel/Start button area If mx>71 And mx<154 Then menu:=3; Else If mx>166 And mx<248 Then menu:=4; End; End; wt:=5; End; End; If menu>2 Then If menu=3 Then Invert_P(G1,72,187,153,213); Else Invert_P(G1,166,187,247,213); End; wt:=wt-1; If wt=0 Then If menu=3 Then mode:=0; menu:=-1; wt:=13; Else Return ai*10+starting; // Return encoded AI settings End; End; End; End; // Handle keyboard input for quick cancel or selection If keyDown=0 Then If IsKeyDown(4) Then keyDown:=1; If mode=1 Then menu:=3; wt:=5; Else menu:=0; wt:=13; End; Else If IsKeyDown(30) Then keyDown:=1; If mode=1 Then menu:=4; wt:=5; End; End; End; Else If IsKeyDown(4)=0 and wt=13 Then keyDown:=0; End; End; // Reset mouse state if no input is detected If mx=-1 And my=-1 Then mouseDown:=0; End; // Update the timing for the next frame t:=t+33; Until 0; // Infinite loop; exit handled by user input returning from the function End; //------------------------------------------------------------------------------ // Gameplay() // Handles the main game loop for a match of Reversi. // It manages three stages: // mode 0: Waiting before fade-in transition. // mode 1: Fade-in transition effect and board initialization. // mode 2: Active gameplay, including move input, disc flipping animations, // score updates, and AI move processing. //------------------------------------------------------------------------------ Gameplay(setup) BEGIN Local t, mode=0, c=(120,120), board, d=0, r=Exp(i*Pi/180), animDelay, processing=0; // processing: 0-waiting, 1-thinking, 2-game over Local cnt=0, anim={}, pulse=5, pulseDir=-.5, currPlayer=0, mouseDown=0, score={2,2}, dispScore={0,0}; Local invalid, b = {c+190*1, c+190*Exp(i*Pi/4), c+190*Exp(i*2*Pi/4), c+190*Exp(i*3*Pi/4), c+190*Exp(i*4*Pi/4), c+190*Exp(i*5*Pi/4), c+190*Exp(i*6*Pi/4), c+190*Exp(i*7*Pi/4)}; Local availMoves={{3,3},{4,3},{5,3},{6,3},{3,4},{6,4},{3,5},{6,5},{3,6},{4,6},{5,6},{6,6}}, ai, starting, aiMove={}, aiCnt, bestAiScore=-9; // Decode setup parameters: if setup < 10 then it sets the starting player; // otherwise, decode AI settings and starting player from the setup value If setup<10 Then starting:=setup; Else ai:=IP(setup/10); starting:=FP(setup/10)*10; End; board:=MakeMat(0,8,8); // Initialize game board as an 8x8 matrix InitGameGraphics(ai); // Setup the game board graphics and grid t:=Ticks-50; Repeat // Synchronize frame timing for smooth animations If Ticks3 And my>3 And mx<236 And my<236 Then If mouseDown=0 Then mouseDown:=1; // Convert mouse coordinates to board indices. mx:=IP((mx-4)/29)+1; my:=IP((my-4)/29)+1; invalid:=1; // Check if the selected move is available If Pos(availMoves,{mx,my}) <> 0 Then Local deltaX, deltaY, currX, currY, discsToFlip={}, index, flipCrd; Local currDisc=IfTE(currPlayer = 1, 1, 21), opponentDisc=IfTE(currPlayer = 1, 21, 1), moveScore; // Check all eight directions for flippable opponent discs For deltaX From -1 To 1 Do For deltaY From -1 To 1 Do If deltaX = 0 And deltaY = 0 Then Continue; End; discsToFlip := {}; animDelay:=200+Size(anim)*300; currX := mx + deltaX; currY := my + deltaY; While (currX >= 1 And currX <= 8 And currY >= 1 And currY <= 8) Do If board[currX, currY] = 0 Then Break; End; If board[currX, currY] = opponentDisc Then animDelay:=animDelay+300; discsToFlip[0] := {{currX, currY}, opponentDisc-animDelay}; Else Local discCnt = Size(discsToFlip); If board[currX, currY] = currDisc Then If discCnt>0 Then For index From 1 To discCnt Do flipCrd := discsToFlip[index,1]; board[flipCrd[1], flipCrd[2]] := currDisc; End; moveScore:=moveScore+discCnt; anim:=Concat(anim,discsToFlip); End; Break; End; End; currX := currX + deltaX; currY := currY + deltaY; End; End; End; // If at least one disc will flip, update board and score If moveScore > 0 Then invalid:=0; board[mx,my]:=currDisc; availMoves:=remove({mx,my},availMoves); For deltaX From mx-1 To mx+1 Do For deltaY From my-1 To my+1 Do If deltaX >= 1 And deltaX <= 8 And deltaY >= 1 And deltaY <= 8 And board[deltaX, deltaY] = 0 Then availMoves[0] := {deltaX, deltaY}; End; End; End; availMoves:=Union(availMoves); anim[0]:={{mx,my},IfTE(currPlayer=1,705,717)}; score[currPlayer]:=score[currPlayer]+1; DrawScore(currPlayer,score[currPlayer]); dispScore:=score; score:=score+IfTE(currPlayer=1,{moveScore,-moveScore},{-moveScore,moveScore}); End; End; // If move is invalid, show a cross animation If invalid and board[mx,my]=0 Then anim[0]:={{mx,my},422}; board[mx,my]:=23; End; End; Else mouseDown:=0; End; End; End; End; // End of main logic handling // Process animations for disc flips and invalid move indications If mode=2 Then Local anCnt, crd, brdCrd, anItem; For anCnt From Size(anim) DownTo 1 Do anItem:=anim[anCnt]; crd:=anItem[1]; brdCrd:=(crd-1)*29+1; If anItem[2]>0 Then DrawIcon(G2,brdCrd,anItem[2],True); End; Case If anItem[2]>99 Then anItem[2]:=anItem[2]-100; End; If anItem[2]<0 Then anItem[2]:=anItem[2]+100; End; If board[crd[1],crd[2]]>anItem[2] Then anItem[2]:=anItem[2]+1; End; If board[crd[1],crd[2]]0 Then currPlayer:=starting; starting:=0; Else currPlayer:=IfTE(currPlayer=1,2,1); End; ResetDiscs(); pulse:=5; pulseDir:=-.5; End; Local canMove,cant=0; // Check if any valid moves remain; if not, switch turns or end game Repeat canMove:=0; For cnt From 1 To Size(availMoves) Do If FlipCount(availMoves[cnt,1],availMoves[cnt,2],board,currPlayer,True) > 0 Then canMove:=1; Break 2; End; End; If canMove=0 Then // No available moves for the current player. cant:=cant+1; If cant=2 Then // GAME OVER: No moves available for both players Rect_P(G2,45,92,195,138,#AF000000); TextOut_P("GAME OVER",G2,61,102,7,#FFFFFF); processing:=3; currPlayer:=0; Break; End; // The turn is passed to the opponent. currPlayer:=IfTE(currPlayer=1,2,1); End; Until canMove=1; If cant=1 Then // Display a cross icon on the player's turn indicator to signal the lack of moves. DrawIcon(G0,{268,IfTE(currPlayer=2,39,157)},22,False); End; // If it's the AI's turn, set processing flag for AI move computation. If ai=currPlayer Then processing:=1; End; End; Continue; End; anim(anCnt):=anItem; End; // Smoothly update the displayed score If NOT Eq(score,dispScore) Then If d Mod 2=0 And Size(anim)=0 Then dispScore:={dispScore(1)+(dispScore(1)score(1)),dispScore(2)+(dispScore(2)score(2))}; DrawScore(1,dispScore[1]); DrawScore(2,dispScore[2]); End; End; // Draw the board Blit_P(G1,4,4,G2); // Draw a pulsating indicator for the current player's turn If currPlayer>0 Then If pulse=IP(pulse) Then Case If currPlayer=1 Then Blit_P(G6,G5,20,34,47,61); DrawIcon(G6,{0,0},pulse,False); Blit_P(G6,268,39); End; If currPlayer=2 Then Blit_P(G6,G5,140,34,177,61); DrawIcon(G6,{0,0},22-pulse,False); Blit_P(G6,268,157); End; End; End; pulse:=pulse+pulseDir; If pulse=1 or pulse = 5 Then pulseDir:=-pulseDir; End; End; End; t:=t+50; // AI Thinking Process: When it's the AI's turn, evaluate available moves If ai=currPlayer And processing=1 And Eq(score,dispScore) Then Local localScore, localMove; While aiCnt < Size(availMoves) Do aiCnt:=aiCnt+1; localScore := FlipCount(availMoves[aiCnt,1],availMoves[aiCnt,2],board,currPlayer,False); If localScore = 0 Then Continue; End; // Modify score based on move position; discourage moves in risky areas and favor corners localMove := availMoves[aiCnt]; If Pos({{2,1},{2,2},{1,2},{7,1},{7,2},{8,2},{1,7},{2,7},{2,8},{7,7},{8,7},{7,8}},localMove)<>0 Then localScore := localScore - 2; Else If localMove[1]=2 Or localMove[1]=7 Then localScore := localScore + 1; Else If localMove[2]=2 Or localMove[2]=7 Then localScore := localScore + 1 End; End; If localMove[1]=1 Or localMove[1]=8 Then localScore := localScore + 10; End; If localMove[2]=1 Or localMove[2]=8 Then localScore := localScore + 10; End; End; // Choose the best move based on the scoring If localScore = bestAiScore Then aiMove[0]:=localMove; Else If localScore > bestAiScore Then bestAiScore := localScore; aiMove:={localMove}; End; End; If Ticks>t-10 Then Break; End; End; // Once all moves are evaluated, trigger the AI move If aiCnt = Size(availMoves) Then processing:=0; bestAiScore:=-9; aiCnt:=0; End; End; Until GetKey()=4; // End gameplay loop on ESC key press 1; // Return state to signal end of gameplay and return to title screen End; //------------------------------------------------------------------------------ // Main Entry Point (START) // This is the main loop that manages the game state: // state = 1: Title screen // state = 2: Player vs. Player game // state > 9: Player vs. AI game (state encodes AI parameters) // state = 0: Exit game //------------------------------------------------------------------------------ START() Begin Local state=1; Repeat Case If state=1 Then state:=Title(); // Show title screen and get next state based on user input End; If state=2 Then state:=Gameplay(1); // Start a Player vs. Player game End; If state>9 Then state:=Gameplay(state); // Start game in Player vs AI mode (open AI parameters) End; End; Until state=0; End;