From 918496ed2b95c9b4f64d6003f83da0e74527eeae Mon Sep 17 00:00:00 2001 From: Berack96 Date: Tue, 19 Dec 2023 02:13:12 +0100 Subject: [PATCH] TrisGUI - added tris GUI - added more tris tests - added new methods for helping with tris winner --- .../java/net/berack/upo/ai/problem2/Tris.java | 87 ++++++--- .../net/berack/upo/ai/problem2/TrisGUI.java | 172 ++++++++++++++++++ src/main/resources/tris/value_empty.png | Bin 0 -> 194 bytes src/main/resources/tris/value_o.png | Bin 0 -> 3290 bytes src/main/resources/tris/value_o_red.png | Bin 0 -> 3339 bytes src/main/resources/tris/value_x.png | Bin 0 -> 3013 bytes src/main/resources/tris/value_x_red.png | Bin 0 -> 3022 bytes .../net/berack/upo/ai/problem2/TestTris.java | 119 +++++++++--- 8 files changed, 321 insertions(+), 57 deletions(-) create mode 100644 src/main/java/net/berack/upo/ai/problem2/TrisGUI.java create mode 100644 src/main/resources/tris/value_empty.png create mode 100644 src/main/resources/tris/value_o.png create mode 100644 src/main/resources/tris/value_o_red.png create mode 100644 src/main/resources/tris/value_x.png create mode 100644 src/main/resources/tris/value_x_red.png diff --git a/src/main/java/net/berack/upo/ai/problem2/Tris.java b/src/main/java/net/berack/upo/ai/problem2/Tris.java index d1922ae..7c40557 100644 --- a/src/main/java/net/berack/upo/ai/problem2/Tris.java +++ b/src/main/java/net/berack/upo/ai/problem2/Tris.java @@ -2,8 +2,10 @@ package net.berack.upo.ai.problem2; import static net.berack.upo.ai.problem2.Tris.Symbol.EMPTY; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; +import java.util.List; /** * Classe che rappresenta il classico gioco del tris, dove per vincere bisogna @@ -93,7 +95,7 @@ public class Tris implements Iterable { * @throws UnsupportedOperationException nel caso in cui si ha già avuto un vincitore */ public void play(int x, int y) { - if(this.haveWinner() != Symbol.EMPTY) throw new UnsupportedOperationException("The game has already finished!"); + if(this.getWinner() != Symbol.EMPTY) throw new UnsupportedOperationException("The game has already finished!"); if(!isPlayAvailable(x, y)) throw new IllegalArgumentException("The state to modify must be Empty!"); this.tris[this.index(x, y)] = this.currentTurn; @@ -126,7 +128,7 @@ public class Tris implements Iterable { * @return un array di coordinate disponibili per giocare. */ public Coordinate[] availablePlays() { - if(this.haveWinner() != EMPTY) return new Coordinate[0]; + if(this.getWinner() != EMPTY) return new Coordinate[0]; var count = 0; for(var i = 0; i < this.tris.length; i++) @@ -148,10 +150,10 @@ public class Tris implements Iterable { * Indica se il gioco è finito. * Il gioco finisce se si ha un vincitore o se non ci sono più caselle vuote. * - * @return vero se iol gioco è finito + * @return vero se il gioco è finito */ public boolean isFinished() { - if(haveWinner() != EMPTY) return true; + if(this.checkTris() != null) return true; for(var symbol : this.tris) if(symbol == EMPTY) @@ -159,35 +161,66 @@ public class Tris implements Iterable { return true; } + /** + * Se si ha un vincitore restiruisce le coordinate delle celle in cui si ha tris. + * @return le coordinate delle celle del tris o null se non si ha ancora un vincitore. + */ + public List getWinnerTris() { + var list = this.checkTris(); + if(list == null) return null; + + var coord = new ArrayList(); + for(var index : list) { + coord.add(new int[] { + index % Tris.LENGTH, + index / Tris.LENGTH + }); + } + return coord; + } + /** * Indica se si ha un vincitore e restituisce chi ha vinto. * @return EMPTY se non c'è ancora un vincitore, altrimenti restituisci il vincitore */ - public Symbol haveWinner() { - // top left corner -> horizontal and vertical - var state = this.tris[0]; - if(state != Symbol.EMPTY) { - if(this.tris[1] == state && this.tris[2] == state) return state; - if(this.tris[3] == state && this.tris[6] == state) return state; + public Symbol getWinner() { + var check = this.checkTris(); + if(check == null) return EMPTY; + return tris[check[0]]; + } + + /** + * Funzione privata per il controllo del tris. + * Nel caso ci sia un tris questo metodo restituisce il valore + * degli indici di dove si trova. + * + * @return un array degli indici del tris, altrimenti null; + */ + private int[] checkTris() { + var possibleTris = new int[][] { + // top left corner -> horizontal and vertical + {0, 1, 2}, + {0, 3, 6}, + // bottom right corner -> horizontal and vertical + {8, 7, 6}, + {8, 5, 2}, + // central -> diagonals, horizontal and vertical + {4, 0, 8}, + {4, 6, 2}, + {4, 3, 5}, + {4, 1, 7} + }; + + for(var check : possibleTris) { + var symbol = this.tris[check[0]]; + + if(symbol == EMPTY) continue; + if(symbol != this.tris[check[1]]) continue; + if(symbol != this.tris[check[2]]) continue; + return check; } - // bottom right corner -> horizontal and vertical - state = this.tris[8]; - if(state != Symbol.EMPTY) { - if(this.tris[7] == state && this.tris[6] == state) return state; - if(this.tris[5] == state && this.tris[2] == state) return state; - } - - // central -> diagonals, horizontal and vertical - state = this.tris[4]; - if(state != Symbol.EMPTY) { - if(this.tris[0] == state && this.tris[8] == state) return state; - if(this.tris[6] == state && this.tris[2] == state) return state; - if(this.tris[3] == state && this.tris[5] == state) return state; - if(this.tris[1] == state && this.tris[7] == state) return state; - } - - return Symbol.EMPTY; + return null; } /** diff --git a/src/main/java/net/berack/upo/ai/problem2/TrisGUI.java b/src/main/java/net/berack/upo/ai/problem2/TrisGUI.java new file mode 100644 index 0000000..2254868 --- /dev/null +++ b/src/main/java/net/berack/upo/ai/problem2/TrisGUI.java @@ -0,0 +1,172 @@ +package net.berack.upo.ai.problem2; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JSeparator; + +import java.awt.Color; +import java.awt.GridLayout; + +/** + * Classe che permette di visualizzare graficamente il gioco Tris + * In questa classe si trova un main che crea una istanza di questa finestra + * + * @author Berack96 + */ +public class TrisGUI extends JFrame { + + public static void main(String[] args) { + new TrisGUI(); + } + + /** + */ + private static final ImageIcon IMAGE_X; + private static final ImageIcon IMAGE_O; + private static final ImageIcon IMAGE_X_RED; + private static final ImageIcon IMAGE_O_RED; + private static final ImageIcon IMAGE_EMPTY; + static { + var loader = TrisGUI.class.getClassLoader(); + IMAGE_X = new ImageIcon(loader.getResource("tris/value_x.png")); + IMAGE_O = new ImageIcon(loader.getResource("tris/value_o.png")); + IMAGE_X_RED = new ImageIcon(loader.getResource("tris/value_x_red.png")); + IMAGE_O_RED = new ImageIcon(loader.getResource("tris/value_o_red.png")); + IMAGE_EMPTY = new ImageIcon(loader.getResource("tris/value_empty.png")); + } + + private final MyComponent[][] buttons = new MyComponent[Tris.LENGTH][Tris.LENGTH]; + private Tris tris = new Tris(); + private TrisAi ai = new TrisAi(this.tris); + private JCheckBoxMenuItem aiFirst; + + /** + */ + private TrisGUI() { + super("Tris"); + + var grid = new GridLayout(Tris.LENGTH, Tris.LENGTH); + var panel = new JPanel(grid); + for(var i = 0; i < Tris.LENGTH; i++) { + for(var j = 0; j < Tris.LENGTH; j++) { + var comp = new MyComponent(Tris.LENGTH, j, i); + panel.add(comp); + this.buttons[j][i] = comp; + } + } + + this.add(panel); + this.attachMenu(); + + this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + this.pack(); + this.setResizable(false); + this.setLocationRelativeTo(null); + this.setVisible(true); + } + + /** + * Metodo utile per non mettere tutto nel costruttore. + * Qui viene creata la menubar + */ + private void attachMenu() { + var menuBar = new JMenuBar(); + + var menu1 = new JMenu("Game"); + var item1 = new JMenuItem("Reset"); + item1.addActionListener(action -> this.reset()); + menu1.add(item1); + + var separator = new JSeparator(); + menu1.add(separator); + + var item2 = new JCheckBoxMenuItem("AI Enabled"); + item2.setSelected(this.ai != null); + item2.addChangeListener(action -> this.ai = item2.getState()? new TrisAi(this.tris):null); + menu1.add(item2); + + this.aiFirst = new JCheckBoxMenuItem("AI First"); + this.aiFirst.setSelected(false); + menu1.add(this.aiFirst); + + menuBar.add(menu1); + this.setJMenuBar(menuBar); + } + + /** + */ + public void reset() { + this.tris = new Tris(); + this.ai = this.ai == null? null:new TrisAi(this.tris); + if(this.ai != null && aiFirst.getState()) this.ai.playNext(); + redraw(); + } + + /** + * Dopo questo metodo la finestra verrà ridisegnata (sempre se ci sono stati dei cambiamenti) + * Nel caso in cui sia stata precedentemente calcolata la soluzione, + * allora verrà evidenziata una tessera con il colore rosso per indicare + * la mossa migliore da fare per la risoluzione. + */ + public void redraw() { + for(var arr: this.buttons) { + for(var button: arr) { + var value = tris.get(button.x, button.y); + var newIcon = switch(value) { + case VALUE_X -> IMAGE_X; + case VALUE_O -> IMAGE_O; + case EMPTY -> IMAGE_EMPTY; + }; + + if(!newIcon.equals(button.getIcon())) + button.setIcon(newIcon); + } + } + + var icon = switch(this.tris.getWinner()) { + case VALUE_X -> IMAGE_X_RED; + case VALUE_O -> IMAGE_O_RED; + default -> null; + }; + + if(icon != null) + for(var coord : this.tris.getWinnerTris()) + this.buttons[coord[0]][coord[1]].setIcon(icon); + } + + /** + * Classe privata usata come appoggio per la gestione dei pulsanti. + * Qui vengono disabilitate alcune impostazioni base dei bottoni e + * viene creato un listener per quando viene premuto su di esso. + */ + private class MyComponent extends JButton { + final int x; + final int y; + + private MyComponent(int n, int x, int y) { + this.x = x; + this.y = y; + + this.setBorder(BorderFactory.createLineBorder(Color.BLACK)); + this.setBackground(Color.WHITE); + this.setContentAreaFilled(false); + this.setFocusable(false); + + this.setIcon(IMAGE_EMPTY); + this.addActionListener(action -> { + if(tris.isPlayAvailable(x, y) && !tris.isFinished()) { + tris.play(x, y); + if(ai != null) ai.playNext(); + redraw(); + } + }); + } + } +} diff --git a/src/main/resources/tris/value_empty.png b/src/main/resources/tris/value_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..e1bc56c1af961daac06850139a16d96dfa3a2dee GIT binary patch literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!EjF($B>G+w+9UwfxJTtzWV3#$SVMulz<(p Y85#N+7*}rFVdQ&MBb@08zmqrvLx| literal 0 HcmV?d00001 diff --git a/src/main/resources/tris/value_o.png b/src/main/resources/tris/value_o.png new file mode 100644 index 0000000000000000000000000000000000000000..b53913bc85b4e448c7744d4c7fc724affc40514c GIT binary patch literal 3290 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 JxY-b$^dEndLJj}` literal 0 HcmV?d00001 diff --git a/src/main/resources/tris/value_o_red.png b/src/main/resources/tris/value_o_red.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb5e0c491da42233b9b10f901e5f7b5605c6458 GIT binary patch literal 3339 zcmds3=U3Cs*8LGeq}R{{rAm_?lqwzRJt!z*Xrf35MLGdOlopyoklu>|UWA7x1f>QD z(nYCC5do%vop7%sI0^>_;|M#w?7d86gN_F*Px?qhjp8Mo&wf zzb|L}p#n{?ov}VtPZ8XtZs1;emUnJhCK{SD2zo|+AO!tGNB18HT9T6k_wSF4i~vW+J}$0C2He#OL8 z4?)i%=)~N7e0+RtY;5-2IdJ1r83?5Huky{QL6dItT)uo{f!-)zmfy1bo-mALQXFraDtn zN+D>NpC1s3z|s=L#(t#++`Cr+L5C$Ji?XsHJ9}toXj)9{u(%jFJA;ITqlyX;896#S z`axP6L`Q>Lw-(Ny-?(rAgoc8|#9u~6Kg!ENVBi;Zb%4PD0^zHc*4UXdATAELxq)lf zb}wC;5)}nku7L1xke0TisrgTM#AGqw~eThI`rNJ}NG+V-actrmh`ASsJnSjBX(a^%0pNfqejF1p|C@p9ZnlYdE#weSPg^=r>r$xVq+Ou!htYz_UO8S9$Wo_q zi(Q5}h?Sp94UO_`@03tD;LP1Od+yC=Vx6lysaTe0g?_Cx+X(yk+AI%s^YK-LBpcR= z3yHm#K%^_^_Pf*MMImbX|BZ8Hx>;9$9nEdko7LzN7WJZ2L#l^vv6`?Iw<2!Ur^v-p zbIv^HRa;~qnTQ&`dLjDDhb5v`=)_hdF0)4c;4l`>?Px!ZoG5wZs!?G5S@di>iv>RO|G-vQZ0`UP95`9ub^ECstw+@6L0nn*@)|H^&QW2euH5 ze#&T)(QL0Z&G7?bZVfZ0d7$SP#N^{U!9E2S|M2r`)-)#59@8w0#3X#o2=zp@u5vnx z%IaURv^lvn$E>UlzlchHIwCCFeDh*_YBz}n$8lkZ!DlEYlkH&x$+19xlGI*#;&?Hh z(4dQd#IW{)eoj7lBesb2KHLitw5}Uz!!t%JgzZnb3^%mtR7l>4BalkeCh|ov_sGYQ zdpx;kt?99fS3Kp@ad_V)j#at0WX=Q{y=c*3?9{#IM>Ub3ld4D=YFE<{y|zRqPeJvT zm3M*s&U;9cr|bjDr{%*Dhs(VySyi1IfeP47K5=ymw%3d*nR4n;>%RPjedd^!9wib3 zY8gSoA5=an8!PmT1u*So=OHe89PK1wtyB)<5DfII_Q;%3EktC2;m^51ml;WJ7wZ16^2 zhlj18P_t%(P*N^6GsCkxZktPOWcfu znCm=7URy4&vv5PNHz~__pPTYx^dc<7*|?{q8UdL^naY0<8)q6di>z>A^qn0B8|aFc zx6)b57Z_Irt31fmqd(DJTcXISQ(nk-&6M^;!|=ou+CZ+~4w}Z4DZS#O`Ns+kTNJ6) zCx7q$TS9>F$B}%;Nr4&a{o1K&|EMV2ee*c`@F_`yN#hi3^msc>HmPhI^_Vu!x>YD} z0iIZy-#1)-?R&j!lDiWMzffXf6o7SFK#a?lOC(?7KEX>f_Hac!UuqSynul+bt4*2f zliuqQAJu*s9Z9g6J_uB1>vdQ*i>K%#dN>+hg}69B6b@P6-rE27n*va2j%4+`i~iY0 zN-CEP#>o-CzG(HotRwwM7e>OkBV>icY&k^6lVK_+0e6xJ5QCh z<2zy(GO+E~3=H>wq_V9Iuhq027axWy>%`+p`zlu~`sR2ZEe%)drkyse3*O+_-QO0$ zDDYYgkiyuuJZ$;CNzc0B!&$=>wq0N?-u>>*G_p6QZUkhwwsyFu9hn^?t+8VQ#Z3ix z7@AzlZY7C}CGh;QtS*E#O|i)OF;iLbsTy6> z8Ib4FDYimC^PyFiQP+GYN4$sOQm1irH_5m-uP>jLE>>_c z$1<9E)7s`B;FrKDf#WXUZyb$6s+yB8)<*Czi|xJ7S=DwnR9Wo*&7SD7#zaqfoSqSO z^KO0?*VI*|-lOkzD6s3BnW`BLyI+N3y=Q{FU|>BYqnKRnS6sHDW7RIh#1={SlcR)T zBfT>5wo3P_Azp%VWrhD%_feJk8_zXtLu{0qpnIOK*~Ap+lp^?3?x&(B_Iq)QlMz2x z51&onBt`4^c~g?uQN^bloFs zlRWmdZ;UCU7sl*%JBolSNJe*Af`{tf3?V ztNo~!Qr_y|^Pcnl^8O3oJ?FX4xj)={&OPUz=eYz2dn+~;K^7Vs8aBAKx#K0{{sEBg zGJo4DIJ+bO#?i`@rgKqv_p+c5FtIbCq3Ov3{kp|KLqmJb0pVn^xVY%)>1kqO;_dC7 zot-^0GD08_goK2|#l>Z0WF#deH8nLU6w1@5Pb(@as8lMMOfD)aYH4YSi;H7pV-ph- zh12=EpR903N78Vv16qJ^h=H=z> z=;%;UQIV6AgTY`53JMP&K1@kTK_ZbNA|mqg@)8mf0s;cAuC7Z=H{NAovo~_R8mq>R8-W`(i$5ZYinzxqoZ41Ue?jk0fWJOe0;&d!LhNi zrlzK{vaC@$t8B-!?NdyK?1kw}EY;r90SmX?;~<>mSL`3MBU*w{ESGLl3h>FVmDP^k3u^rogJ z91eHw+BHK%Lk$g$pr9ZO24iPu7aAH`P*8x!;}a4R)YQ~!YHA)oer#)N>*(k>K0aPw zU*Ff)=kM=7F)`8I-QCd8U}R*JnVAWNLOnb@0s;b_KYyN+lcTMz?dRt=KR+K564Kh* z3Ic&fM@LIbOU=#Ab8~a=-o4Ag!C`G}Jvcb1udna!?(X2=(ACwIn3&kx+w1J??Be25 zS63Gi5s{RX#Lmu+Mx!4*cyRyz{pjduPEO9ts|X7Vv$3(^;^LZ|oUE>{1^@t!jg5~U zJ$mxwNqc+y@bIvOg+*3YR&#T6Mn*P3M2>W{zj5(@5HwIErQmWQX0){{hK7cH>K_0o;VphNG(Z)&xrtMZ*XN3} z=+$YFi5|8;0;_15t2N7DD63vi*COf+B>mo>nhIaRjp)EyT(&MP9xcF6t0`eeE0b(0 zUI&-A!lmQ+DjF65b}zJc>x|na&zhIE)@)u?A2`=|j~r8fe5S;?1z8>qZH*)N@)`U9 zuCRa6#6{S|Q1ge9uiU7^aOpUXzB|^GP>zX~>tmV`D}FJql`}Xdv5FP@yKWSxE85Tc z7C-EAER~%BS%bL*Ai+`C>JMy4*d}9PHGy13Ao{yuP#)PcxP$qffjZE1)faf}ih8(_ zcGImph9KCg@nZ42=P`aH?;~OMVBL& zx|iV?q$f+gEEPc7p38d1o1!s?rFCK{$x~f9TqpPiGPG2zG6RuOU5ejtH+F|NpX=Fs zEXZ7^-$nkcI7n@SkK*9L>mB^(Fi{D^n?C?E_>e2+Z@?R{{y`9e{)9FKwcWh%bfrYj z(@WAvX~;apy?6A8r=&_Ul&J-{$3#zhv7lf2`4ytogc|0+{IWMXaB~kZo1t(a(IWr; zvissr6v{}`E~^^AO(B^TCar2EnZJ1Rc#O+K`YRygcuCatLbwyjGG)x@T(i4Y9^A1j zh5rCQk400TF^%B74TybR%)S!QqI5U&YJ@kBSLWnAl0z^4x<*=)FcR0f_oG-NAdllu z(~nckouF(lPwF3@;K_tg{W7975V-iz?c2o4fUJAj*og+Z0O8WzptF9Per}z#DhG$n zv_^yv4WI5DjrH&$LfOXz_C>!4lSZXJ*3j{qP2%!rK}<HfgIr;y|KO(WaS z_Y@r>q&a{pBk^ZXGK#3o+dX|en|e^l_Dmq|c;mE2NrC&I#(v&Wh@Wp4Km$YQeOJSeT5s#YE#t7t@Wbhawg5 z8%nIVwV6l-Dsj4 z@vvVHdCDY4u_O=XI_{A@_HZbTx3N)m7LYaP3v4u*zU`IfYc!89L&<)MVZ}E9357lT z{ls76wTT#B?#-j0igVx{Fbcm~!at@R&md8)eD2mPX?}I25V-hMKY>?=(Kxrgz?bYG1osO(d6rz7QFE zzbT?{Sfy85}Pycdwajj)d}|vDRGcYD3whS+>G+s~+nsf_m(NEJLr>I6ALV?+06kvv4s;+ehTa*X$DE-L=tzS(EPjKP_&>R(*P@)V#se zO6Q-0PHR$0$bDtM6cD{8QBoCJT`zC_RSz>Fb#ulfzQcaIQnFX{ha2v{n}R9{TU@ z+HzubSj65PCK$QU05z@(9lc}IVLJ11>q~$l2S0+nr!DclfZKbV5VY!y^M!`<1;H36 zG^cMwo{0=0Y|wLCt>Bt;_caQA-~Pvw{jn^DB~xm82yzaTO~(V;Fph)JEc}9RnDH+H zF-up=EUk26Rn8UmND8qipwT9BQjJR>n^!_Cd5Z(-E`^1qY}30$vMQl8cZ@zdTZ~#D zBj4+MvN0^*4+o=HRD<7XPppo|Br&!SLj<=bBh%JRxDgy+`YM_Eo?nhb*EZd071J9) zv$4&Sy4YP4$feNr;pMz4F=0*R=(lQ9#Y%0&DJBzhu+Xw+{>ytj4No?>x76BC@!^%{ zvu`8*ZhucZ22~t9dQSb#&Iv!msP?c0g}o6x?pBgTEexBMMDE9Vr&upa;ERKcjLU!U zbN082b(24qeERz)m5nn9D92G@Ur`#L$Y^f2$JnkDTBZ{n|HW6^q;WfRNwY0}?E&$E zyhh`{TNAe(oju(^CN*Y4z*Ja0o*>8zz@{&Ku*II)nQdG-mi?*Fo@9 zuv$PfEsOo7Ow0lIw!dfEJ7Y3l|8peYh^X0LqVi2~hmJLvoh3_UpD!}%r~_!s*RCyz t@2;u;Nj)?+|Cc@g&#WgdurSVFB7ERS%fFNpt1sUP8n}hMd8a8V<-d?8($)X~ literal 0 HcmV?d00001 diff --git a/src/main/resources/tris/value_x_red.png b/src/main/resources/tris/value_x_red.png new file mode 100644 index 0000000000000000000000000000000000000000..f1d6549224e6fb686daeb49c21bbbca8577bdffd GIT binary patch literal 3022 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 R`cm%+2y19+K+(); + trisPos.add(new int[] {0, 0}); + trisPos.add(new int[] {1, 0}); + trisPos.add(new int[] {2, 0}); + + var trisCoord = tris.getWinnerTris(); + trisCoord.sort((a, b) -> Arrays.compare(a, b)); + assertEquals(trisPos.size(), trisCoord.size()); + for(var i = 0; i < trisPos.size(); i++) + assertArrayEquals(trisPos.get(i), trisCoord.get(i)); + + // diagonal \ O tris = new Tris(); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(2,1); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(1,1); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(2,0); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(2,2); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(1,2); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(0,0); - assertEquals(VALUE_O, tris.haveWinner()); + assertEquals(VALUE_O, tris.getWinner()); assertTrue(tris.isFinished()); + trisPos = new ArrayList(); + trisPos.add(new int[] {0, 0}); + trisPos.add(new int[] {1, 1}); + trisPos.add(new int[] {2, 2}); + + trisCoord = tris.getWinnerTris(); + trisCoord.sort((a, b) -> Arrays.compare(a, b)); + assertEquals(trisPos.size(), trisCoord.size()); + for(var i = 0; i < trisPos.size(); i++) + assertArrayEquals(trisPos.get(i), trisCoord.get(i)); + + // vertical 2 column X tris = new Tris(); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(1,0); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(0,2); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(1,1); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(0,1); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(1,2); - assertEquals(VALUE_X, tris.haveWinner()); + assertEquals(VALUE_X, tris.getWinner()); assertTrue(tris.isFinished()); + trisPos = new ArrayList(); + trisPos.add(new int[] {1, 0}); + trisPos.add(new int[] {1, 1}); + trisPos.add(new int[] {1, 2}); + + trisCoord = tris.getWinnerTris(); + trisCoord.sort((a, b) -> Arrays.compare(a, b)); + assertEquals(trisPos.size(), trisCoord.size()); + for(var i = 0; i < trisPos.size(); i++) + assertArrayEquals(trisPos.get(i), trisCoord.get(i)); + + // No winner tris = new Tris(); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(0,0); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(1,0); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(2,0); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(1,1); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(0,1); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(0,2); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(2,1); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(2,2); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertFalse(tris.isFinished()); tris.play(1,2); - assertEquals(EMPTY, tris.haveWinner()); + assertEquals(EMPTY, tris.getWinner()); assertTrue(tris.isFinished()); + assertNull(tris.getWinnerTris()); } + @Test + public void testWinner2() { + var tris = new Tris(); + + tris.play(1,1); + assertFalse(tris.isFinished()); + tris.play(0,0); + assertFalse(tris.isFinished()); + tris.play(0,1); + assertFalse(tris.isFinished()); + tris.play(2,1); + assertFalse(tris.isFinished()); + tris.play(0,2); + assertFalse(tris.isFinished()); + tris.play(2,0); + assertFalse(tris.isFinished()); + tris.play(1,0); + assertFalse(tris.isFinished()); + tris.play(2,2); + assertTrue(tris.isFinished()); + } }