Saturday 25 March 2017

Coding Naughts and Crosses... Underestimated!


3 months in with learning code, the honeymoon period is seriously over. One of the most annoying things about it is learning all the huge amount of information through videos and copying examples, then trying your own stuff and realising that you don't actually understand it at all.

I've been pretty impatient to be honest. I've wanted to get to some "good" stuff, like Zelda/Bomberman top down platforming games, and/or card games like solitaire and big 2. But getting stuck at certain points really stumps your momentum.

This realisation that I've been jumping the gun was when I decided to try and make Naughts and Crosses. "OMG, too EZ", I thought, this will take me a few hours tops.

Dammit, this is harder then I thought! And I've been learning there is a negative attitude towards programmers that what they do is easy.

One of the things that makes coding so important, is the fact that you are solving problems in ways that need you to think differently then what you would expect. Making Naughts and Crosses seems simple but has complexity and needs creative thinking.


X or O... OR NOTHING?

Note that I'm using the P5.js library for Javascript. TLDR; my full code is at bottom.



What seems like a pretty simple game gains in complexity as you reach the end. Starting out drawing a board, the X's and O's are quite easy to do. The hard part is putting them within the areas clicked, alternating them, and then eventually have the code work out whether there is 3 in a row of X's and O's, or all the spaces are filled for a draw.

Oh, we also have to make it know if a space has been filled or not.

I started out with making variables of cols and rows, made a loop to draw out the board. Then I did 9 boolean (true or false) expressions for whether the space was filled at all.

Then I added a counter which is 0, 1 or 2, but when its 2 it resets back to 0. After each symbol is in its position 1 is added to the counter.

So we can make an if statement to be like, if the mouse is clicked within position x and y numbers (lets say top left), AND it has counter 1 AND var TL is true, then draw circle there. Since the counter starts at 0, circle is always first. After that it must be 1, which is a cross and so on...

var cols = 3;
var rows = 3;

var TL = true;
var TM = true;
var TR = true;
var ML = true;
var MID = true;
var MR = true;
var BL = true;
var BM = true;
var BR = true;

var counter = 0;

function setup() {
createCanvas(600, 600);
drawBoard(0);
}

function draw() {
if (counter == 2) {
counter == 0;
}
result ();
}

function mouseClicked() {
if (mouseX >= 0 && mouseX <= 200 && mouseY >= 0 && mouseY <= 200 && TL == true) {
if (counter == 0) {
new Naught (0, 0);
}
if (counter == 0) {
new Cross (0, 0);
}
counter++;
TL = false;
}


... repeat 8 more times with positions adjusted.


MAGIC SQUARES NOT MAGICAL ENOUGH

After a bit of googling some ideas on how to check if there was a winner/draw, I came across the nostalgic math problem from secondary school (that's high school for u Americans) of Magic Squares!



I could say something like;

If row/column/diagonal = 15... then its a winner!
If numbers of all spots added up = 45 then its a draw!

So I created another variable that gave each position the number of the magic square and voila!

if (oScore + xScore == 45){
   text("Its a draw!", 300, 300);
    }
if (oScore == 15) || if (xScore == 15) {
    text("Its a winner!");
    }

Finished! EZ!

Well... no. This method works, if you get 3 in row straight away, but if you get more then that, ie another turn on the board that isn't part of the row, it will not add up to 15.

The very cool way of using magic squares is quashed. Not so magical after all...




2D ARRAY

What was needed was a better way of storing the data on each position and whether it had an X or an O in it. What we needed was a 2D array.

An array is a list stored using [ ] symbol. A number in between it represents the position in the list. The lists always start with 0. So [1] would be position 2. Arrays are really useful because they can manipulate lists and can be interchangeable for its purpose. If I programmed a ball, I could put that ball object in an array and have loads of them bouncing all over the place.

So a 2D array, is a list within a list. So for us here, we want an array of 3, and within that 3 we want another 3 for 9 spaces. Array [0] would have another array in it of [0][0]. [0][1]. and [0][2]. That could be the top row. Confused? Good, coz I'm just getting my head round it too.

So we start off by making the list;

var pos = [];
pos[0] = [0, 0, 0];
pos[1] = [0, 0, 0];
pos[2] = [0, 0, 0];

We can already see the way it is written out that it kinda represents its position. 0 = empty, 1 = O, and 2  = X.

I also improved upon the code by using the floor function to find the mouse clicked position. The floor function rounds the number always to the lowest whole number.

The new code for the top left would be like this;

function mouseClicked() {
x = floor(mouseX / 200);
y = floor(mouseY / 200);

if (x == 0 && y == 0 && TL == true) {
if (counter == 0) {
new Naught(-200, -200);
pos[0][0] = 1;
}
if (counter == 1) {
new Cross(-200, -200);
pos[0][0] = 4;
}
counter++;
TL = false;
}
repeat 8 times adjusted for position...

And my stupidly large code to check winner would be like this;

function result() {
this.x = 300;
this.y = 325;

//[Down][Across] Starting from top left

var topRow = pos[0][0] + pos[0][1] + pos[0][2];
var midRow = pos[1][0] + pos[1][1] + pos[1][2];
var botRow = pos[2][0] + pos[2][1] + pos[2][2];

var leftCol = pos[0][0] + pos[1][0] + pos[2][0];
var midCol = pos[0][1] + pos[1][1] + pos[2][1];
var rightCol = pos[0][2] + pos[1][2] + pos[2][2];

var diagDown = pos[0][0] + pos[1][1] + pos[2][2];
var diagUp = pos[2][0] + pos[1][1] + pos[0][2];

var sumBoard = topRow + midRow + botRow;

noStroke();
fill(0);
textSize(72);
textAlign(CENTER);

if (topRow == 3 || midRow == 3 || botRow == 3 || leftCol == 3 || midCol == 3 || rightCol == 3 || diagUp == 3 || diagDown == 3) {
text("NAUGHTS WIN!", this.x, this.y);

} else if (topRow == 12 || midRow == 12 || botRow == 12 || leftCol == 12 || midCol == 12 || rightCol == 12 || diagUp == 12 || diagDown == 12) {
text("CROSSES WIN!", this.x, this.y);

} else {
if (sumBoard == 21) {
text("ITS A DRAW!", this.x, this.y);
}
}
}


DIRTY CODE BAH!


So this mostly works. There are still some issues where you can still continue the game after it has found a winner, I'd like to have some resetting going on.

I also think that I could probably shorten the blank square checking by linking it to my array. Also, a loop looks like in order to help with the hideousness of my code repeated 9 times.

There are multiple ways of coding this game, some are better then others by being more efficient. By efficient, the coding community agrees its the least number of letters possible. But for learning and starting out, that's not always the best way to learn. Dirty code for beginners means they actually finish, whilst others get stuck and possibly give up. Improvements can come later on, as long as one is aware that its dirty, then its ok.



Anyways outdone myself again, just like my code, this blog post exceeds its recommended number of words, happy coding ppl!


No comments:

Post a Comment