Defining and checking a 2D string array
Hey.
I am creating a tic tac toe board game with 3 rows and 3 columns (a 2D array). When I input, for example far more than 3 characters for a row into the console, it always returns that the board is valid. My isValid method is not working evidently. Im not sure if it is becuase of improper intiialisation of the board class, game class, or the method itself. Please let me know!
Board.cs relevant code:
Game.cs relevant code:
Relevant program.cs code: (cannot change this)
Just looking for a bit of guidance!
190 Replies
Your current constructor for the board class seems to do nothing but create a new 3x3 array, which is a valid board. You never use the
lines
parameter.How would i go about using the lines parameter
do i create a loop that populates the 3x3 array with the lines parameter?
^^^^ if so how would i do that?
You might first want to consider what should happen if
lines
is not valid.
Since you are using a 2d array, you can't meaningfully construct anything valid from a lines array that has 10 items all of varying lengths.
Are you taking raw user input? Are you validating it in some way?The input is just read off of the console via ReadLine()
And the user input is either X, O or .
I apologise for possible poor explaining Iām just having trouble understanding it all myself
It's all good.
If you want to be able to check validity after construction the board, you could do some checks in the constructor and if lines does not follow the correct format, you could set board to some invalid state.
Like making it size 0 or something.
Ohhhhh
So right now the board is just always initialised as 3x3 right
So it will never be not valid
Yes
Exactly
Riiight
So you want to check if lines has the proper format, and then use that to fill in the "slots" of your array.
Right
I guess you first check if lines is of length 3, if each string in it is of length 3, and if no string contains any invalid characters.
if any of those are wrong, you set the board to size 0 and return early.
That would be a rough solution I guess.
Otherwise you can proceed to generate the 3x3 array again and fill it manually.
If you are struggling, I could try to whip up a suggestion.
I do not expect you to spend ample time on it but a rough solution would go a long way
I will have a go at it right now as well
Something like this for the first bit I guess. Haven't tested it though.
that works exactly as intended
thanks so much for that! that is one less headache
ill try implement the other checks now
Any questions about how it works or is that reasonably readable?
so i understand this part clearly:
it just sets the board to 0x0 which is obviously invalid
so then the isValid() method returns false
Yes
if (lines.Length != 3
this bit just means if the line length is anything expect 3
followed by or operator
```lines.Any(x => x.Length != 3 x.All(c => !"XO.".Contains(c))))``` this bit is a little confusing
but like i kinda understand but cant explain t
Ok so...
Yes, we only want to do execute that block if the lines array is invalid. This is true IF: the number of lines is not 3 OR if the following condition is true for
Any
of the strings in lines
:
x is just giving a name to the current string we are checking, so we can refere back to it.
so the condition is:
if the current string is not of length 3 OR if this additional condition is true for All
the elements of the string (i.e. the characters its made of): c
is again giving a name to the current element we are checking, in this case the individual characters of the string. For each character, check if the string "XO." contains that character, in other words, if the character c
is one of the 3 valid ones.
To be clear, x
and c
are just single letters by convention, you could name them anything you want.
x => just says "take each element of the sequence I am working with, name it x
and then perform the action to the right of the arrow"
very roughly speaking i just changed it to that for my brain, thats all good right?
Yes, that's totally fine.
i have read everything you said too im not ignoring it just interjecting
I got it š
right i think i understand all of that
I'm not sure my explanation is all that useful. I probably could have worded it a little better. Just wrote it off the top of my head tbf and I've only just gotten out of bed š
no no trust me
you made plenty of sense hahaha, even with a fresh brain
now i need to implement a few more rules... will this thread stay open for a bit? i might work thru them and send anything i get stuck with (i dont expect you to hang around)
just for other people too if they see it
Oh wait I'm sorry
I messed up I think
Give me a second
no worries no woorries
It shoudl stay open yes.
I think I twisted the condition around in the wrong way
The
All
method does the wrong check right now
It's only true if EVERY character is wrong
But we want it to be true if even a single one is off.lucky you caught that
My bad, so that really ought to be:
I meant it to check if ALL characters are valid, but it really should check if ANY characters are not valid, if that makes sense š
yep that makes complete sense
perfect
I think you could also remove the
!
to achieve the same effect, but this way should be more efficient because it quits early if any strings do not fulfill the condition.ohhhhh right
Generally its going to be faster to check if any of set of values are invalid, than to check if all of them aren't š
Not that it really matters for this tiny size, but its still good to keep in mind.
ahhh right
if its good practice i might as well use it
so i get familiar
I'm definitely a fan of doing the "proper" thing even if it makes no tangible difference to performance, especially if it adds zero additional complexity to your code.
Actually, I lied again.
LOL
I guess just dont listen to me, because I'm a moron
Is todays lesson
All
also quits early.
We are literally just inverting the condition.
Any
quits early, if it finds any string which contains invalid characters.
All
quits early, if it finds any string which does not contain valid characters.
The they do literally the same thing, we are just inverting the condition with !
.LOL
right
any will still work for this though thankfully right? becuase as long as theres one that is incorrect, it will be invalid
My previous statement still holds, its just not relevant to this use case. In this case we can just do whatever seems more readable to us š
its not like we need 3 to be invalid
Exactly
yep i get you
One is too much already, and both methods will quite at the first issue.
Any
-> "I found an invalid one, so thats enough for me", All
-> "I found an invalid one, so at least one is invalid meaning they can't all be valid, so thats enough for me" ši presume the first one is supposed to be Any
yep haha
Fixed it š
ok that makes sense right
i learnt something new today thats great
Noice
So now at any point after that block, your data should be valid and you can construct the board.
This is how ive started the next bit, basically X always moves first, so if X has a crazy amount of moves then its also invalid. I've defined these counters prior to the if statement, do you tihnk this is the correct way to go about this? (This is directly below in line with our last statement)
yep i did like 20 tests it seems perfect
also i understand if you are done helping, no pressure ^^
No its fine
Public holiday around these parts, and I'm free as a bird.
ohhh same here sweet as
So... you could technically chain this check into the first if
Since this is also still part of the validity check for the input.
It might make it a little rough to read though, depending on how you do it...
ooooo okay
i mean what would you recommend
if its of any interest- ive mostly split out stuff like this into mulitple statments around other parts of my code, so it would look more like 'me' i guess if i do it seperately
but im open to anything
In pseudo code something like: if the difference between the sum of the count of "X"s in each string and the count of "O"s in each string is more than 1, return false.
I think this is probably not a bad approach.
You could do this as a bit of a crude one-liner with more linq methods (like
All
and Any
), but maybe its cleaner to just do it manually.
Like you have now, keep a running total of all X's and O's.
Loop over the elements of lines
.
Then in a nested loop, loop over the characters of the current line and add up the X's and O's I guess.
after the loops you can check the difference between the two, and maybe once again just set an invalid board and return.right
ive done this as a start, so i should put this after the loop taht counts up the values?
Between the variable declations and the if should be the loops.
Also, the
else
needs to go.ohh yep removed that
You can also directly take the difference like so:
Math.Abs(amountX - amountO) > 1
ohhh right
i.e. take the absolute value (remove sign if negative) of the difference between the two and check if its within a distance of 1
does Abs = Absolute Value
right
Yep š
right right
that seems pretty straight forward
so ive got the counts both defined, and that part done
just the for loop
which is the hard part
Since you don't need to modify anything about the lines, you could start with a simple foreach.
its underlining as if to say the syntax is incorrect, why is that
just for the foreach bit
Oh I'm so sorry.
I've been using another language that doesnt use brackets š
foreach(var line in lines)
my badohhh right no worries!
ok thats working
Now for each string you want to loop over the individual characters.
right
ive done this but it seems to say "Cannot assign to 'playerInput' because it is a 'foreach iteration variable'
"
"
==
for comparisonohh right, now it says "Operator '==' cannot be applied to operands of type 'char' and 'string'"
Also, although I don't know if it matters here, you can declare a character literal (rather than a string) by using 'X' instead of "X"
Ok so it does matter š
ohhhh right
maybe that does matter
ahahaha
I couldn't remember if that was converted automatically. Seems it is not.
ok right i think thats working
Another tiny thing, you could make that second if an else if. Currently it gets checked, even if the first one was already true.
Its not going to matter, but its arguably more "correct" š
ohhhh right
so it saves the program checking it unnecessarily
Yeah
Honestly, its so minor, and actually the compiler might even optimize this away, but its still better to be clear about your intention.
The condition you are checking might be more involved, to where it actually does matter if you are doing unnecessary work.
right no i get you
In this case, the difference is going to be no more than statistical noise, but still š
yep i get you
alright sweet
i think thats everything i needed for right now
Perfect
thank you so much for explaining everything as you did it, i feel lik ei actually know what ive typed out rather than just getting someone to do it for me
and regurgitating it with no idea what im doing
That was my hope š
I try to do that as much as I'm capable.
Well, let me know if anything else comes up, I'll be happy to take a look.
you certianly succeeded
i will let you know! thanks so much
@hiyosilver sorry for the ping!!! just seeing if you are about
Out and about indeed.
What's up?
legend
alright so ive spent this morning creating all the methods needed for the last bit
so in one class, i have a heap of methods such as GetDiagonal(), GetRow() and GetColumn()
and in another class, i need to use these classes to read who wins the game (whether the diagonal, row or column = "XXX", "OOO" or other)
basically im not sure which part is incorrect at the moment, whether its the GetDiagonal() etc. methods or the GetWinner() method itself
ill provide all the code now, its a bit to digest so take as long as you need and feel free to brush it off if its too much
Game.cs class:
Board.cs class:
I was distracted making breakfast for a bit, I'll check it out now š
no worries and no rush, please enjoy your breakfast first
Thanks!
So... about this
Just to clarify, you say you aren't sure what is incorrect about it?
What exactly isn't working as you expect?
alright so basically
yesterday we fixed the validity issue and thats all fine
today, when i enter a game
for instance (a valid game where the winner should be X):
OXX
OOX
. . X
it returns that there is no winner
I see
no matter what i put in, if the game is valid, it is always no winner
I had this conversation with this person as well, they introduced me to the debugger
but to be honest im kinda inexpreienced and dont understand how to mak eit work for me
I was just about to suggest that aswell
right
No rush, it can be a bit rough to wrap your head around at first.
Just generally its probably a good idea to get used to it, when you eventually run into a problem where you simply need to use it
But maybe we can untangle this particluar issue by just looking at the code š
As a first step I would suggest a simplification
right
what exactly would you suggest first
HasWinner and GetWinner share basically all of their logic
You could combine them into a single method, by adding an
out
parameter to HasWinner().
Like so:ahhh i see
just a note sorry; for the outlines of this task i must use them as two seperate functions
as they are both called invidiually in the Program.cs and i am not permitted to atler that
Oh, its a requirement
Never mind then š
yep yep
haha i shouldve said that a minute earlier sorry about that
basically all the methods ive got are required
So you are not allowed to change their signatures either?
as in, add parameters to them?
we can add parameters yeah
when i started the code, get winner looked like this
GetWinner()
{
HandleExceptionSomething(something);
}
it was like a placeholder thing
so i can do whatever id like within that
If you did something like this
If you have to do the checking logic anyway, you might aswell write the winner to an out parameter right away
In this case, an extra
GetWinner
method is redundant, but if you have to have it you could just do
Well, ok, this is not going to help you with your current problem, so maybe I'm moving in the wrong direction atm šhahaha
ur all good its no owrries, its very inteersting if nothing else
I'll actually look at the rest now
alrighty thanks so much
let me know if you need anything else ^
So the person you talked to earlier suggested that the board might simply be empty
Just out of interest, could you put something like
Console.WriteLine($"Line: {diagonalString}");
in your GetDiagonal method?
Just to see what the value of the string is just before it gets returned?
If debugging is too annoying, a few judiciously placed print statements often do the trick šsorry ill do that write now i got sidetracked
i like that idea ill do that right now
not write now*
interesting
that is what is outputted
Yeah so it seems like the board is in fact empty
Can you show the current workflow for getting from inputting a board to the array being filled?
maybe you are still missing the actual construction after the validation
*of the input
would this be in the constructorS?
I suppose so
It was yesterday, if I recall correctly š
okay so this is the board constructor in Board.cs:
how do i send code like you so its easier to read btw
with colours
if you are using the three backticks block
you can just put "cs" after the first 3
ahhhh
and this is the only construcotr in the Game.cs Class:
Yeah so, this looks like it still doesn't ever set a valid board.
i see
It does all the checks against the input and invalidates the board if its wrong, but it never uses the values to construct a correct board.
That should be at the end below the last conditional thats checking the counts of X's and O's
ooooooohhhhhhhh
so basically all it does is creates this value that says valid or not and it never actually creates the game
so none of my methods() can even do anything meaningful
So really just two nested loops with 3 steps each, then assign
board[x, y] = lines[x][y]
Or something like thatahhh right
Yep, thats the issue š
Your logic is probably fine, you just have no valid data to work on.
riiiiiiight
As a sanity check, you could add a "Print" method to your Board class.
that uses Console.Writes's to print out the state of the board line by line.
right
so where abouts would i integrate that
im having trouble seeing where
OR would it be in the loop i create itself
Integrate the construction of the array or the printing?
the printing
i mean both but that was directed at the printing haha
Well for the print, you would just put a new method in the Board class, like the constructor:
But I would focus on creating the board for now š
ok ok haha
i will have a go at it.....
I edited my post to show roughly what I mean.
You could use a similar approach for building the board array.
Except instead of write you assign to
board[x, y]
sorry its just taking me a second to figure out how to write it haha
ive got this at the moment but it looks noting like yours im just testing with making them all X
is that fundamentally what that does
Yeah, this is totally fine.
In fact probably better.
You have already checked for validity, so you know its 3x3, no need to get dimensions again š
Now instead of assigning X, you need to get the corresponding character
from the correct row (aka the string
lines[row]
)right
oh wait so does that mean my two for statements dont matter
becuase we already know its 3x3... my brains a little slow right now im having trouble writing it
No, they are fine.
I just meant, checking against a limit of 3 explicitly makes more sense, rather than getting the dimensions again like I did.
ohhhh right
yep yep i get that ok
You could just do
board[0, 0] = lines[0][0]
or something, for each of the 9 boxes, but thats just ugly. The loops are the better approach šohhhh that makes sesne right
so like that?
I think so.
Give it a whirl and see if the print statement from earlier now gives the proper result.
Or anything at all would be nice š
hmmm
still saying the same thing
let me refresh and make sure
Crap... :/
yeah still saying that... damn.
Can you put another WriteLine
sure, where abouts
Console.WriteLine($"{lines[row][column]}");
after where you assign, just so we can see each character as its beign assigned
Just interjecting- is it in the right spot before we try anything else?
oh!
interesting
Should be correct yes
Ok that looks good
The code is reached, and the characters look right.
Well you can remove that so it doesnt spam your console š
Can you post just your diagonal method again?
So I don't have to scroll up.
yep sure!
sorry was grabbing some water
Oh I see š
Sorry I should have been more explicit.
The print statement needs to be at the bottom
Just before the return
Or the string is only ever going to empty.
So it seems like this discovered a bug by accident xD
OH whoops haha
so thats correct i think
top left to bottom right and vise versa
Looks good
Now it still says there is no winner apparently.
yeah....
i might try one where someone wins diagonally
give me one sec
oh!
it works diagonally
FYI, there might be a bug in your code for getting rows and columns, but we'll see when we get there.
I saw it earlier and forgot to mention.
GetRow and GetColumn both access the first dimension of the board array.
ohhhh
riiight
shall i put a print statement similiar to waht you did for the diagonals on the end of the getrow function first?
and then the getcolumn once we figure that out>
also is this the bug you were talkng about
it should be [i, column] instead right
So sorry, I got distracted
Yes I believe that should do it.
or switch the other one maybe. I always get confused by what is what in 2d arrays.
All good Iāve had to head out briefly anyway so it works out
Iāll put a readline through to make sure they are reading the right ones
with the current implementation, this is what is returned
Looks good-ish?
i think its all sorted then now
let me run it through some tests to make sure
Remember to clean up any superfluous console writes afterwards š
yep i just did that haha thank you
so the ONLY case
is when O wins by reverse diagonal
so from top right to bottom left
this is the only time it returns an incorrect response
thats the diagaonal thing once again
Where is the comparison?
Where the result of this is actually checked.
It might be as easy a flipped condition somewhere.
perhaps the logic is off ehre?
yes it is
should be
oh wait omg
its right in front of me
yeah haha
ive got two left diaongals for the OOO
yes you could just change that too š
ill do you way it looks a little nicer haha
your*
My suggestion just shortens it a little more. If you are evaluating a boolean expression and returning a boolean based on the result, you can just return the expression directly in a single line.
ahhhh right
i assume thats good/common practice too?
In my capacity as just some guy hobbyist amateur, I say yes.
No just kidding, yes I'm pretty confident in claiming that returning boolean expressions directly is the idiomatic way of doing it.
hahaha
alright ill take your word for it
alright the program passes all 18 possible combinations
im all done now!
thanks so much for your help over the past day, i am truly indebtted
Happy to help š
Good luck with any further projects!
Feel free to hit me up if you are stuck on anything else. No guarantees I can help, but I can atleast take a look.
thanks so much, i appreciate that immensely... i will let you know!
have a good rest of your day :)