Rules to evaluate poker hands

Here's a crazy example that's been on my mind lately. Imagine that we've expressed a poker hand in RDF. I'm going to be flexible as to exactly how it's done, but an RDF list would work.

 :myHand :isPokerHand (
     [] :value :King ; :suit :Spades.
     [] :value :King ; :suit :Hearts.
     [] :value :Ace ; :suit :Clubs.
     [] :value :Seven ; :suit :Hearts .
     [] :value :Eight ; :suit :Diamonds .
 ) .

in this case, I want to infer

 :myHand a :Pair .

I'd like to have types that correspond to the types of hands that occur in poker.

 :myHead :beats :someOtherHand .

if :someOtherHand is, say, a pair of :Queen(s). I'm happy to see any method of inference used, and as mentioned before, if you can do it with a :Bag or using a property to represent hand membership, that's find with me too. Of course, a complete working system is a lot to ask, but a rough design would count towards a useful answer

It's an absolute cinch with OWL 2:

Semantics of the cards first:

:HeartCard :equivalentClass [ :oneOf ( :2H :3H ... :KH :AH) ] , [ owl:hasValue :Hearts ; owl:onProperty :suit ] [ owl:hasValue :magic ; owl:onProperty :heart ] .
:SpadeCard ...
:ClubCard ...
:DiamondCard ...
:2Card :equivalentClass [ :oneOf ( :2H :2S 2C :2D ) ] , [ owl:hasValue :2 ; owl:onProperty :suit ] ; :next :3Card .
:2 :nextValue :3 .
...
:ACard ...
:A ...
:Card owl:equivalentClass [ owl:unionOf ( :HeartCard :SpadeCard :ClubCard :DiamondCard ) ] , [ owl:unionOf ( :2Card ... :ACard ) ] ; owl:hasKey ( :suit :value ) .
[ a owl:AllDifferentFrom ; owl:members ( :2H :3H ... :KD :AD ) ] .
:2H a :2Card , :HeartCard .
...
:AD a ...

A little glue:

:heart rdfs:subPropertyOf :notSpade , :notClub , :notDiamond .
:spade ...
:notHeart owl:inverseOf :heartNot .
...
:next rdfs:subPropertyOf :lessThan ; owl:propertyChainAxiom ( :hasValue :nextValue ) .
:equal a owl:ReflexiveProperty .
:equal rdfs:subPropertyOf :lessThanEqual . :lessThan rdfs:subPropertyOf :lessThanEqual .
:lessThan a owl:TransitiveProperty ; owl:IrreflexiveProperty ; owl:inverseOf :greaterThan .
:sameHandNext ~todo, possible?~
:sameHandLessThan ...
:straightCheck owl:propertyChainAxiom ( :hasCard :sameHandNext :sameHandNext :sameHandNext :sameHandNext ) .
:suit owl:inverseOf :suitHas .
:differentSuit owl:propertyChainAxiom ( :heart :heartNot ) , ( :spade :spadeNot ) ; owl:propertyDisjointWith :sameSuit .
:sameSuit owl:propertyChainAxiom ( :suit :suitHas ) .
:negFlushCheck owl:propertyChainAxiom ( :hasCard :sameHandDifferentSuit ) .
:sameSuitDifferentCard ~todo; full enumeration?~
:sameValueDifferentCard ~todo; full enumeration?~
:highestCard owl:propertyChainAxiom ( :hasCard :sameHandlessThanEqual :sameHandlessThanEqual :sameHandlessThanEqual :sameHandlessThanEqual ) ; owl:inverseOf :highestCardOf .
:2pair3OfAKindCheck owl:propertyChainAxiom ( :sameHandLessThan :sameHandLessThan :highestCardOf  ) .

...and then you're ready to define some semantics to classify different hands:

:PokerHand rdfs:subClassOf [ owl:cardinality 5 ; owl:onProperty :hasCard ] .
:FlushHand [ owl:intersectionOf ( :PokerHand [ owl:complementOf [ owl:someValuesFrom owl:Thing ; owl:onProperty :negFlushCheck ] ] ) ] .
:StraightHand owl:intersectionOf  ( :PokerHand ; [ owl:someValuesFrom :Card ; owl:onProperty :straightCheck ]  ) .
:StraightFlushHand owl:intersectionOf ( :FlushHand :StraightHand ) .
:StrictStraightHand owl:intersectionOf ( :StraightHand [ owl:complementOf :StraightFlushHand ] ) .
:StrictFlushHand owl:intersectionOf ( :FlushHand [ owl:complementOf :StraightFlushHand ] ) .
:RoyaleFlushHand owl:intersectionOf ( :StraightFlushHand [ owl:someValuesFrom :ACard ; owl:onProperty :hasCard ] ) .
:4OfAKindHand owl:intersectionOf ( :PokerHand [ owl:unionOf ( [ owl:qualifiedCardinality 4 ; owl:onProperty :hasCard ; owl:onClass :2Card ] ... [ owl:qualifiedCardinality 4 ; owl:onProperty :hasCard ; owl:onClass :ACard ] ) ] ) .
:3OfAKindHand owl:intersectionOf ( ... ) .
:2OfAKindHand owl:intersectionOf ( ... ) .
:FullHouseHand owl:intersectionOf ( :3OfAKind :2OfAKind ) .
:2PairHand owl:intersectionOf ( :PokerHand [ owl:intersectionOf ( [ owl:someValuesFrom owl:Thing ; owl:onProperty :2pair3OfAKindCheck ] [ owl:complementOf :3OfAKind ] ) ] )
:Strict3OfAKindHand owl:intersectionOf ( :3OfAKind [ owl:complementOf :FullHouseHand ] ) .
:Strict2OfAKindHand owl:intersectionOf ( :2OfAKind [ owl:complementOf [ owl:unionOf ( :FullHouseHand :2PairHand  ) ] ] ) .
:StrictHighCard owl:intersectionOf ( :PokerHand [ owl:complementOf ~everythingAbove ] ) .
:6HighCard owl:intersectionOf ( :PokerHand [ owl:someValuesFrom :6Card ; owl:onProperty :highCard ]  ) .
...
:Strict7HighCard owl:intersectionOf ( :StrictHighCard [ owl:someValuesFrom :7Card ; owl:onProperty :highCard ] )
...

And then defining the ordering of different (strict) hands is a piece of cake using the principle of "All Elephants are Bigger Than All Mice"... in this case, all StraightFlushs beat all FourOfAKinds, etc., all 7Highs valueBeat all AHighs, etc., then chain beatsameHandTypevalueBeat and drawsameHandTypesameValue.

...see, it's a cinch.

Ok so here's my attempt at doing this with just RDF and SPARQL.

Here's my sample data:

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@prefix : <http://example.org/poker/>.

:Ace a :Card .
:King a :Card ;
:beatenBy :Ace .
:Queen a :Card ;
:beatenBy :King .
:Jack a :Card ;
:beatenBy :Queen .
:Ten a :Card ;
:beatenBy :Jack .
:Nine a :Card ;
:beatenBy :Ten .
:Eight a :Card ;
:beatenBy :Nine .
:Seven a :Card ;
:beatenBy :Eight .
:Six a :Card ;
:beatenBy :Seven .
:Five a :Card ;
:beatenBy :Six .
:Four a :Card ;
:beatenBy :Five .
:Three a :Card ;
:beatenBy :Four .
:Two a :Card ;
:beatenBy :Three .

:me :hasPokerHand [ :containsCard [ :value :King ; :suit :Hearts ] ,
[ :value :King ; :suit :Spades ] ,
[ :value :King ; :suit :Clubs ],
[ :value :Three ; :suit :Diamonds ],
[ :value :Seven ; :suit :Hearts ] ] .

:opponent :hasPokerHand [ :containsCard [ :value :Ace ; :suit :Diamonds ],
[ :value :Four ; :suit :Clubs ],
[ :value :Ace ; :suit :Spades ],
[ :value :Ace ; :suit :Hearts ],
[ :value :Nine ; :suit :Clubs ] ] .

:opponent2 :hasPokerHand [ :containsCard [ :value :Seven ; :suit :Clubs ],
[ :value :Eight ; :suit :Clubs ],
[ :value :Two ; :suit :Clubs ],
[ :value :Jack ; :suit :Clubs ],
[ :value :Ace ; :suit :Clubs ] ] .

:opponent3 :hasPokerHand [ :containsCard [ :value :Three ; :suit :Spades ],
[ :value :Four ; :suit :Spades ],
[ :value :Five ; :suit :Spades ],
[ :value :Six ; :suit :Spades ],
[ :value :Seven ; :suit :Spades ] ] .

Firstly checking we have a valid hand:

PREFIX : <http://example.org/poker/>

SELECT
?player
(COUNT(?card) AS ?cards)
(IF(?cards = 5, true, false) AS ?ValidHand)
WHERE
{
?player :hasPokerHand ?hand .
?hand :containsCard ?card .
} GROUP BY ?player

We can use SELECT queries to determine what hand you have e.g. pairs, three of a kind or four of a kind

PREFIX : <http://example.org/poker/>

SELECT
?player
?value
(COUNT(?value) AS ?count)
(IF(?count = 2, true, false) AS ?Pair)
(IF(?count = 3, true, false) AS ?ThreeOfAKind)
(IF(?count = 4, true, false) AS ?FourOfAKind)
WHERE
{
?player :hasPokerHand ?hand .
?hand :containsCard ?card .
?card :value ?value
} GROUP BY ?player ?value HAVING (COUNT(?value) > 1)

Full house starts to get a bit more complicated since we're looking for a pair and a 3 of a kind. We can do this by using the previous query as a subquery and then using an IF to determine if this is present:

PREFIX : <http://example.org/poker/>

SELECT
?player
IF (?ThreeOfAKind && ?Pair, true, false) AS ?FullHouse)
WHERE
{
{
SELECT
?player
?value
(COUNT(?value) AS ?count)
(IF(?count = 2, true, false) AS ?Pair)
(IF(?count = 3, true, false) AS ?ThreeOfAKind)
(IF(?count = 4, true, false) AS ?FourOfAKind)
{
?player :hasPokerHand ?hand .
?hand :containsCard ?card .
?card :value ?value
} GROUP BY ?player ?value HAVING (COUNT(?value) > 1)
}
}

Flushes are relatively simple:

PREFIX : <http://example.org/poker/>

SELECT
?player
?suit
(true AS ?Flush)
WHERE
{
?player :hasPokerHand ?hand .
?hand :containsCard ?card .
?card :value ?value ;
:suit ?suit .
} GROUP BY ?player ?suit HAVING (COUNT(?value) = 5)

Straights are again a bit harder, note that the desired count is 4 and not 5 since the highest card in the straight will not have a card above it in the hand:

PREFIX : <http://example.org/poker>

SELECT
?player
(true AS ?Straight)
WHERE
{
?player :hasPokerHand ?hand .
?hand :containsCard ?card .
?card :value ?value .
BIND(EXISTS {
?value :beatenBy ?higherValue .
?hand :containsCard ?higherCard .
?higherCard :value ?higherValue
} AS ?StraightMember)
FILTER(?StraightMember)
} GROUP BY ?player HAVING (COUNT(?StraightMember) = 4)

From this you could work up to queries that determine whose hand is better though I don't have time to do this right now, may add more later when I have more time...