Vinnaren i pepparkakshustävlingen!
2017-08-28, 07:13
  #1
Medlem
kakelpannas avatar
Jag söker all feedback jag kan få på följande Tre-i-rad spel, vad du än kan komma på. Då tänker jag inte på det estetiska, utan koden osv.

<3

Kod:
!!!!!!!!!!!!!!!!!!!! MAIN.CPP !!!!!!!!!!!!!!!!!!!!!!!!!!!


#include <iostream>
#include "game.h"
#include "ui.h"
#include "scoregame.h"
#include "ai.h"

int main(){
	TicTacToe::Game tictac;

	char c = 0;
	TicTacToe::Player player = TicTacToe::Player::x;
	std::cout<<"\nPlay as x or o? ";
	std::cin>>c;
	if(c == 'o' || c == 'O')
		player = TicTacToe::Player::o;
	else if( c == 'x' || c == 'X')
		player = TicTacToe::Player::x;
	else
		std::cout<<"Invalid choice. Picking X by default. ";

	while(TicTacToe::scoreGame(tictac) == TicTacToe::Winner::game_in_progress){
		TicTacToe::printGame(tictac);
		if(tictac.getTurn() == player){
			unsigned move = TicTacToe::promptSquare();
			if(! tictac.makeMark(move) )
				std::cout<<"Illegal move!"<<std::endl;
		}
		else
		{
			tictac.makeMark( TicTacToe::aiMark(tictac) );
		}
	}
	TicTacToe::printGame(tictac);

	std::cout<<std::endl;
	TicTacToe::Winner winner = TicTacToe::scoreGame(tictac);

	if( ((winner == TicTacToe::Winner::x) && (player == TicTacToe::Player::x)) || ((winner == TicTacToe::Winner::o) && (player == TicTacToe::Player::o)) )
		std::cout<<"You won!"<<std::endl;

	else if (winner == TicTacToe::Winner::draw)
		std::cout<<"Draw!"<<std::endl;
	else
		std::cout<<"You lose :("<<std::endl;

	std::cout<<std::endl;
	return 0;
}


!!!!!!!!!!!!!!!!!!!! PLAYER.H !!!!!!!!!!!!!!!!!!!!!!!!!!!

#pragma once

namespace TicTacToe {
	enum class Player {x, o, none};
}

!!!!!!!!!!!!!!!!!!!! BOARD.H !!!!!!!!!!!!!!!!!!!!!!!!!!!

#pragma once

#include <cstdint>
#include "player.h"

namespace TicTacToe{
	class Board{
		public:
		void clear (); // Set all squares to no marker
		void markSquare (unsigned square, Player marker); // Unconditionally set square to marker
		Player getMark (unsigned square) const;

		private:
		// pattern is divided into 2-bits segments. 0: marked by none. 1: marked by x. 2: marked by o.
		// The 2 least significant bits encode square 1.
		// The Following 2 bits represent square 2, and so forth.
		uint32_t board = 0;
	};
	
}

!!!!!!!!!!!!!!!!!!! BOARD.CPP !!!!!!!!!!!!!!!!!!!!!!!!!!

#include "board.h"


namespace TicTacToe{

	void Board::clear(){
		board = 0;
	}

	void Board::markSquare (unsigned square, Player marker){
		uint32_t mask = 0;
		switch(marker){
			case Player::none:
				mask = 0;
				break;
			case Player::x:
				mask = 1;
				break;
			case Player::o:
				mask = 2;
				break;
		}
		mask <<= ((square-1)*2);
		board &= ~mask;
		board |= mask & 0x3FFFF; // Only allow 9 squares
	}

	Player Board::getMark (unsigned square) const {
		uint32_t mask = board >> ((square-1)*2) & 3;
			if(mask == 1)
				return Player::x;
			if(mask == 2) 
				return Player::o;
			return Player::none;
	}

}

!!!!!!!!!!!!!!!!!!!!!!! GAME.H !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

#pragma once

#include "board.h"

namespace TicTacToe{

	class Game{
		public:
		void reset();
		bool makeMark (unsigned square); // Current player marks square. Turn is flipped. False on illegal move.
		Player getMark(unsigned square) const;

		Player getTurn () const;

		private:
		Player turn = Player::x;
		Board board;
	};

}

!!!!!!!!!!!!!!!!!!!!!!!!! GAME.CPP !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

#include "game.h"

namespace TicTacToe{


	void Game::reset(){
		board.clear();
		turn = Player::x;
	}

	bool Game::makeMark (unsigned square){
		if(board.getMark(square) != Player::none)
			return false;

		board.markSquare(square, turn);

		if(board.getMark(square) != turn)
			return false;

		if(turn == Player::x)
			turn = Player::o;
		else
			turn = Player::x;

		return true;
	}

	Player Game::getMark(unsigned square) const {
		return board.getMark(square);
	}

	Player Game::getTurn () const {
		return turn;
	}


}


!!!!!!!!!!!!!!!!!!!!! UI.H !!!!!!!!!!!!!!!!!!!!!!!!!!!!

#include "game.h"

namespace TicTacToe{
	void printGame(const Game& game);
	unsigned promptSquare();
}

!!!!!!!!!!!!!!!!!!!! UI.CPP !!!!!!!!!!!!!!!!!!!!!!!!!!!


#include "ui.h"
#include <limits>
#include <iostream>

namespace TicTacToe{
	void printGame(const Game& game){
		std::cout<<std::endl;
		for(int s=1; s <= 9; ++s){
			if((s-1)%3 == 0)
				std::cout<<std::endl;
			switch(game.getMark(s)){
				case Player::x:
					std::cout<<"X";
					break;
				case Player::o:
					std::cout<<"O";
					break;
				default:
					std::cout<<"?";
			}
		}
		std::cout<<std::endl;
	}

	unsigned promptSquare(){
		std::cout<<std::endl<<"Your turn (1-9): ";
		unsigned in;
		std::cin.clear();
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
		std::cin>>in;
		return in;
	}

}



!!!!!!!!!!!!!!!!!!!! SCOREGAME.H !!!!!!!!!!!!!!!!!!!!!!!!!!!

#pragma once

#include "game.h"

namespace TicTacToe{

	enum class Winner {game_in_progress, x, o, draw};
	Winner scoreGame (const Game & ); 

}

!!!!!!!!!!!!!!!!!!! SCOREGAME.CPP !!!!!!!!!!!!!!!!!!!!!!!!!

#include "scoregame.h"

namespace TicTacToe {
	Winner scoreGame (const Game & game){
		Player winner = Player::none;

		for(int s=0; s < 3; ++s){
			Player column_who = game.getMark(s+1);
			if(column_who != Player::none && column_who == game.getMark(s+4) && column_who == game.getMark(s+7))
				winner = column_who;

			int row_start = 1+s*3;
			Player row_who = game.getMark(row_start);
			if(row_who != Player::none && row_who == game.getMark(row_start+1) && row_who == game.getMark(row_start+2))
				winner = row_who;
		}

		Player mid_who = game.getMark(5);

		if(mid_who != Player::none && mid_who == game.getMark(1) && mid_who == game.getMark(9))
			winner = mid_who;

		if(mid_who != Player::none && mid_who == game.getMark(3) && mid_who == game.getMark(7))
			winner = mid_who;

		if(winner == Player::x)
			return Winner::x;

		if(winner == Player::o)
			return Winner::o;

		for(int i=1; i <= 9 && game.getMark(i) != Player::none; ++i)
			if(i == 9)
				return Winner::draw;

		return Winner::game_in_progress;
	}	
}


!!!!!!!!!!!!!!!!!!!! AI.H !!!!!!!!!!!!!!!!!!!!!!!!!!!!
#include "game.h"

namespace TicTacToe{

	unsigned aiMark(Game&);
}


!!!!!!!!!!!!!!!!!!! AI.CPP !!!!!!!!!!!!!!!!!!!!!!!!!!

#include <iostream>
#include "scoregame.h"

namespace TicTacToe{

		static int minimax (Game g, unsigned & move) {

			switch(scoreGame(g)){
				case Winner::draw:
					return 0;
				case Winner::x:
					return 1;
				case Winner::o:
					return -1;
				default: break;
			}

			
			int best_score = g.getTurn() == Player::o ? 2 : -2;
			int best_move = 0;

			for(int m=1; m <= 9; ++m){
				Game next(g);
				if(!next.makeMark(m))
					continue;
				int next_score = minimax(next, move);
				if( (g.getTurn() == Player::o && next_score < best_score) || (g.getTurn() == Player::x && next_score > best_score) )
				{
					best_score = next_score;
					best_move = m;
				}
			}
			move = best_move;
			return best_score;
		}

		// Return 0 if no move is available
		unsigned aiMark(Game& g) {
			if( scoreGame(g) != Winner::game_in_progress )
				return 0;
			unsigned move;
			minimax(g, move);
			return move;			
		}

}
Citera
2017-08-28, 08:33
  #2
Moderator
RostigHinks avatar
Jag hade nog gjort en enum class av rutorna med elementen E, X, O. Själva brädet hade jag gjort en array av denna enum typ.

Din enum class Winner liknar mycket tillstånden i en finit tillståndsmaskin. Kan vara värt att utnyttja det designmönstret.
Citera
2017-08-28, 10:16
  #3
Medlem
kaks avatar
Nu används den iofs inte, men jag brukar undvika reset/clear-metoder.
Det är så lätt att missa att återställa hela tillståndet.
Bättre att skapa en ny instans och låta konstruktorn initialisera allt data korrekt.
Citera
2017-08-28, 15:34
  #4
Medlem
kakelpannas avatar
Citat:
Ursprungligen postat av RostigHink
Jag hade nog gjort en enum class av rutorna med elementen E, X, O. Själva brädet hade jag gjort en array av denna enum typ.

Din enum class Winner liknar mycket tillstånden i en finit tillståndsmaskin. Kan vara värt att utnyttja det designmönstret.

Jag började med en array<Player, 9> som ju har elementen {none,x,o}.

Sedan tänkte jag att jag kunde använda mig av något bitmönster för att spara minne när jag kör sökträdet i AIn (och ev också få fler chacheträffar), men också för att snabba upp score-funktionen genom att bara jämföra brädan med några bitmaskar. Så till en början hade scoreGame() också tillgång till brädans rådata via en const funktion.

Det visade sig vara en omständlig idé. Trädet är inte ens minneskrävande med sitt sökdjup 9. Men eftersom att jag redan hade getMark och setMark färdiga så lät jag det vara som det var.

Hur tänkte du med finite machine? Att jag ska köra brädan genom en finite machine eller?

Citat:
Ursprungligen postat av kak
Nu används den iofs inte, men jag brukar undvika reset/clear-metoder.
Det är så lätt att missa att återställa hela tillståndet.
Bättre att skapa en ny instans och låta konstruktorn initialisera allt data korrekt.

Sant, jag håller med. Men det är svårt att motivera en spelbräda-klass som inte går att nollställa.
Citera

Stöd Flashback

Flashback finansieras genom donationer från våra medlemmar och besökare. Det är med hjälp av dig vi kan fortsätta erbjuda en fri samhällsdebatt. Tack för ditt stöd!

Stöd Flashback