mirror of
git://git.savannah.nongnu.org/eliot.git
synced 2024-12-27 09:58:08 +01:00
New MoveSelector class, to help find the "best" move to play.
The best move has the highest possible score, but it is also the one leading to the most interesting game. This is a subjective notion, but some heuristics can help, such as: - a move sparing a blank is better than one using it - a move with many prefixes and suffixes is better than one without extensions - a move "opening" the game is better than one blocking it - a move leaving a nice rack is better than one leaving "bad" letters At the moment, only the first heuristic is implemented.
This commit is contained in:
parent
4e533eed27
commit
664eec36ed
8 changed files with 153 additions and 17 deletions
|
@ -55,6 +55,7 @@ libgame_a_SOURCES= \
|
||||||
cmd/game_rack_cmd.h cmd/game_rack_cmd.cpp \
|
cmd/game_rack_cmd.h cmd/game_rack_cmd.cpp \
|
||||||
cmd/master_move_cmd.h cmd/master_move_cmd.cpp \
|
cmd/master_move_cmd.h cmd/master_move_cmd.cpp \
|
||||||
turn.cpp turn.h \
|
turn.cpp turn.h \
|
||||||
|
move_selector.cpp move_selector.h \
|
||||||
duplicate.cpp duplicate.h \
|
duplicate.cpp duplicate.h \
|
||||||
arbitration.cpp arbitration.h \
|
arbitration.cpp arbitration.h \
|
||||||
freegame.cpp freegame.h \
|
freegame.cpp freegame.h \
|
||||||
|
|
|
@ -74,6 +74,7 @@ Move AIPercent::getMove() const
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// TODO: use MoveSelector to select a correct move
|
||||||
return Move(m_results->get(0));
|
return Move(m_results->get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include "move.h"
|
#include "move.h"
|
||||||
#include "pldrack.h"
|
#include "pldrack.h"
|
||||||
#include "results.h"
|
#include "results.h"
|
||||||
|
#include "move_selector.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "cmd/player_move_cmd.h"
|
#include "cmd/player_move_cmd.h"
|
||||||
#include "cmd/player_rack_cmd.h"
|
#include "cmd/player_rack_cmd.h"
|
||||||
|
@ -286,7 +287,10 @@ void Duplicate::endTurn()
|
||||||
// It's probably not even possible, but let's be safe.
|
// It's probably not even possible, but let's be safe.
|
||||||
throw EndGameException(_("No possible move"));
|
throw EndGameException(_("No possible move"));
|
||||||
}
|
}
|
||||||
setMasterMove(Move(results.get(0)));
|
|
||||||
|
// Select a clever master move if possible
|
||||||
|
MoveSelector selector;
|
||||||
|
setMasterMove(Move(selector.selectMaster(results)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#include "round.h"
|
#include "round.h"
|
||||||
#include "pldrack.h"
|
#include "pldrack.h"
|
||||||
#include "results.h"
|
#include "results.h"
|
||||||
|
#include "move_selector.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "turn_data.h"
|
#include "turn_data.h"
|
||||||
|
@ -458,7 +459,8 @@ PlayedRack Game::helperSetRackRandom(const PlayedRack &iPld,
|
||||||
PlayedRack pldCopy = pld;
|
PlayedRack pldCopy = pld;
|
||||||
|
|
||||||
// Get the best word
|
// Get the best word
|
||||||
const Round & bestRound = res.get(0);
|
MoveSelector selector;
|
||||||
|
const Round & bestRound = selector.selectMaster(res);
|
||||||
LOG_DEBUG("helperSetRackRandom(): initial rack: "
|
LOG_DEBUG("helperSetRackRandom(): initial rack: "
|
||||||
<< lfw(pld.toString()) << " (best word: "
|
<< lfw(pld.toString()) << " (best word: "
|
||||||
<< lfw(bestRound.getWord()) << ")");
|
<< lfw(bestRound.getWord()) << ")");
|
||||||
|
|
73
game/move_selector.cpp
Normal file
73
game/move_selector.cpp
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
* Eliot
|
||||||
|
* Copyright (C) 2013 Olivier Teulière
|
||||||
|
* Authors: Olivier Teulière <ipkiss @@ gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include "move_selector.h"
|
||||||
|
#include "round.h"
|
||||||
|
#include "results.h"
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
|
||||||
|
INIT_LOGGER(game, MoveSelector);
|
||||||
|
|
||||||
|
#define PLAYED_JOKER (-1000)
|
||||||
|
|
||||||
|
|
||||||
|
Round MoveSelector::selectMaster(const BestResults &iResults) const
|
||||||
|
{
|
||||||
|
ASSERT(!iResults.isEmpty(), "Nothing to select from");
|
||||||
|
|
||||||
|
// Easy case
|
||||||
|
if (iResults.size() == 1)
|
||||||
|
return iResults.get(0);
|
||||||
|
|
||||||
|
// Compare the rounds. The one with the highest score wins.
|
||||||
|
// In case of equal scores, the first one wins.
|
||||||
|
int bestIndex = 0;
|
||||||
|
int bestScore = evalScore(iResults.get(0));
|
||||||
|
for (unsigned num = 1; num < iResults.size(); ++num)
|
||||||
|
{
|
||||||
|
int score = evalScore(iResults.get(num));
|
||||||
|
if (bestScore < score)
|
||||||
|
{
|
||||||
|
bestScore = score;
|
||||||
|
bestIndex = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return iResults.get(bestIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int MoveSelector::evalScore(const Round &iRound) const
|
||||||
|
{
|
||||||
|
int score = 0;
|
||||||
|
score += evalForJokersInRack(iRound);
|
||||||
|
// TODO: more heuristics
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int MoveSelector::evalForJokersInRack(const Round &iRound) const
|
||||||
|
{
|
||||||
|
return iRound.countJokersFromRack() * PLAYED_JOKER;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
61
game/move_selector.h
Normal file
61
game/move_selector.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
* Eliot
|
||||||
|
* Copyright (C) 2013 Olivier Teulière
|
||||||
|
* Authors: Olivier Teulière <ipkiss @@ gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MOVE_SELECTOR_H_
|
||||||
|
#define MOVE_SELECTOR_H_
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
class Round;
|
||||||
|
class BestResults;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This helper class uses various heuristics to determine which move
|
||||||
|
* is the "best" for a given situation. At the moment, only one situation
|
||||||
|
* is implemented, namely choosing an appropriate master move for duplicate
|
||||||
|
* games.
|
||||||
|
*/
|
||||||
|
class MoveSelector
|
||||||
|
{
|
||||||
|
DEFINE_LOGGER();
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Return a move to be used as "master move" in a duplicate game.
|
||||||
|
* The method takes the given moves and tries to find the optimal move,
|
||||||
|
* i.e. the move which maximizes the following criteria:
|
||||||
|
* - it uses as few jokers from the rack as possible
|
||||||
|
* - it offers many prefixes and/or suffixes
|
||||||
|
* - it opens the game
|
||||||
|
* - it leaves good letters in the rack
|
||||||
|
* Since these criteria may not reach their maximum for the same move,
|
||||||
|
* some compromises must be done.
|
||||||
|
*/
|
||||||
|
Round selectMaster(const BestResults &iResults) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
int evalScore(const Round &iRound) const;
|
||||||
|
int evalForJokersInRack(const Round &iRound) const;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "rack.h"
|
#include "rack.h"
|
||||||
#include "results.h"
|
#include "results.h"
|
||||||
|
#include "move_selector.h"
|
||||||
#include "pldrack.h"
|
#include "pldrack.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "turn.h"
|
#include "turn.h"
|
||||||
|
@ -224,7 +225,10 @@ Move Topping::getTopMove() const
|
||||||
results.search(getDic(), getBoard(), getHistory().getCurrentRack().getRack(),
|
results.search(getDic(), getBoard(), getHistory().getCurrentRack().getRack(),
|
||||||
getHistory().beforeFirstRound());
|
getHistory().beforeFirstRound());
|
||||||
ASSERT(!results.isEmpty(), "No top move found");
|
ASSERT(!results.isEmpty(), "No top move found");
|
||||||
return Move(results.get(0));
|
|
||||||
|
// Find the most interesting top
|
||||||
|
MoveSelector selector;
|
||||||
|
return Move(selector.selectMaster(results));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include "prefs_dialog.h"
|
#include "prefs_dialog.h"
|
||||||
|
|
||||||
#include "public_game.h"
|
#include "public_game.h"
|
||||||
|
#include "move_selector.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "turn_data.h"
|
#include "turn_data.h"
|
||||||
#include "rack.h"
|
#include "rack.h"
|
||||||
|
@ -533,22 +534,11 @@ void ArbitAssignments::setDefaultMasterMove()
|
||||||
if (results.isEmpty())
|
if (results.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
unsigned currIndex = 0;
|
// Find a good default
|
||||||
unsigned jokerCount = results.get(0).countJokersFromRack();
|
MoveSelector selector;
|
||||||
if (jokerCount > 0)
|
Move move = Move(selector.selectMaster(results));
|
||||||
{
|
|
||||||
for (unsigned i = 1; i < results.size(); ++i)
|
|
||||||
{
|
|
||||||
if (results.get(i).countJokersFromRack() < jokerCount)
|
|
||||||
{
|
|
||||||
currIndex = i;
|
|
||||||
jokerCount = results.get(i).countJokersFromRack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign the master move
|
// Assign the master move
|
||||||
Move move = Move(results.get(currIndex));
|
|
||||||
m_game->duplicateSetMasterMove(move);
|
m_game->duplicateSetMasterMove(move);
|
||||||
emit gameUpdated();
|
emit gameUpdated();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue