2
\$\begingroup\$

I'm trying to learn python and I'm modeling a simple dice game that a friend and I invented.

On any one turn of the game, you must roll a specific combination to stay in the game.

The particular rule I'm trying to model is this:

Given a previous roll (where the dice are contiguous) make another contiguous roll either above the previous roll or before it. 6 and 1 are considered contiguous. Outside of being contiguous the order of the dice does not matter...

some examples

existing roll (3, 4) valid subsequent rolls: (1, 2) (5, 6)

existing roll (5, 6) valid subsequent rolls: (1, 2) (3, 4)

existing roll (6, 1) valid subsequent rolls: (2, 3) (4, 5)

I have written the below python3 code to deal with this aspect of the game. However, being new to python, I'd like to know how experienced programmers would deal with this problem. Is there a smarter way to do this programmatically without all the sorting and comparisons and having to make the two arbitrary evaluations? All comments are welcome, readability, efficiency etc. Thanks for your time.

arguments:

prev = previous/ existing roll

curr = current roll to check

sides = number of sides of the dice

Program:

from itertools import cycle, islice


def chkStk(prev, curr, sides):
    def srt(a):
        return sorted(a, reverse=True if (min(a), max(a)) == (1, sides)
                      else False)

    def cmp(a):
        return a == list(x for x in islice(cycle(range(1, sides+1)),
                                           a[0]-1, a[0]+len(a)-1))
    curr = srt(curr)
    prev = srt(prev)

    return cmp(prev+curr) or cmp(curr+prev)


# examples
print(chkStk([3, 4], [1, 2], 6))
print(chkStk([3, 4], [5, 6], 6))

print(chkStk([5, 6], [1, 2], 6))
print(chkStk([5, 6], [3, 4], 6))

print(chkStk([6, 1], [2, 3], 6))
print(chkStk([6, 1], [4, 5], 6))


# counter examples
print(chkStk([6, 1], [6, 1], 6))
print(chkStk([1, 1], [1, 1], 6))
print(chkStk([1, 2], [4, 5], 6))

output:

True
True
True
True
True
True
False
False
False
[Finished in 0.1s]
\$\endgroup\$
  • 2
    \$\begingroup\$ You should look at the modulo operation, which uses the % binary operator in most C-inspired languages. You can test for (a - b) % 6 == 1 to check contiguity, and abs(a - b) % 6 == 2 to test for adjacency. \$\endgroup\$ – Austin Hastings May 5 at 16:25
3
\$\begingroup\$

Specification

I'm having trouble understanding the problem. I think you roll two dice at once, and the results must be distinct and adjacent to each other and to the previous roll.

Comments

Your question includes some explanation before the program:

I'm modeling a simple dice game that a friend and I invented.

On any one turn of the game, you must roll a specific combination to stay in the game.

The particular rule I'm trying to model is this:

Given a previous roll (where the dice are contiguous) make another contiguous roll either above the previous roll or before it. 6 and 1 are considered contiguous. Outside of being contiguous the order of the dice does not matter...

This should be part of the program! It could be a docstring at the beginning of the file.

arguments:

prev = previous/ existing roll

curr = current roll to check

sides = number of sides of the dice

These should also be part of the program! They should be part of chkStk's docstring, or better yet, included in the argument names. prev and curr could be called previous_roll and current_roll.

srt returns its argument in order, unless it contains both bounds, in which case it's reversed. This is surprising, so it requires an explanation in a comment or docstring.

Names

All three function names are inscrutably short.

  • srt sorts its argument (which should be a two-element list) in a cyclic order. So it could be called cyclic_order.
    • srt's argument a is a 2-die roll (i.e. a pair), so it should be called pair or roll.
  • cmp checks whether its argument is a contiguous ascending sequence (in the same cyclic order). So it could be called contiguous or is_contiguous or is_ascending or even in_order.
    • cmp's argument a is a list of (1-die) rolls, so it should be called rolls.
  • chkStk checks whether curr is a valid roll after prev, so it should be called something like valid_roll or is_valid_roll or `

(It's confusing to have roll mean a pair of 1-die rolls, so maybe the whole program should switch to something consistent, such as "roll" for one die and "pair" for two dice.)

Small simplifications

True if boolean_expression else False can be simplified to just boolean_expression.

[debatable] (min(a), max(a)) == (1, sides) is short, but most people are accustomed to reading min(a) == 1 and max(a) == sides.

Even better: since 1 and sides are the minimum and maximum values possible, you can skip the min and max and just check whether the values are present: 1 in a and sides in a.

list(x for x in foo) can be simplified to just list(foo).

Simpler ways

In cmp, instead of building a sequence in cyclic order and comparing to it, it might be simpler to check that each successive pair is in cyclic order.

There are easier ways to solve this problem.

If my restatement of the problem above ("the results must be adjacent to each other and to the previous roll") is correct, it can be easily turned into a program. You can simply check each part (possibly in a helper function):

  • whether two dice of a pair are adjacent to each other
  • whether two pairs are adjacent to each other

You can do both of these without any list operations.

Tests

Instead of printing out results for you to check, your test cases can check them for you! The simplest way to do this is with assert:

assert chkStk([5, 6], [1, 2], 6)
assert not chkStk([5, 6], [1, 4], 6)
assert not chkStk([6, 1], [2, 1], 6), 'overlapping pairs should fail'

This won't print anything unless the test fails. You can include an optional error message.

\$\endgroup\$
  • \$\begingroup\$ Thanks, this is a great answer. \$\endgroup\$ – mAndroid May 6 at 10:22
2
\$\begingroup\$

Here is how you might do it.

def tuples_adjacent(a, b, modulus) -> bool:
    def sequential(x, y):
        return (y-x) % modulus == 1
    assert sequential(*a) and sequential(*b)
    return sequential(a[1],b[0]) or sequential(b[1],a[0])

This will raise an AssertionError on tuples_adjacent((1,1), (1,1), 6) because the tuples do not meet the precondition of being consecutive pairs. I'm not sure if that is exactly what you want without seeing the surrounding program. You can decide if you actually just want to return False if that precondition is not met.

The other commenter mentioned abs(a-b)%6==2 for checking adjacency, but this is incorrect and fails for the case a=5, b=1. You instead have to do (a-b)%m in {2,m-2}. In general, absolute value and modulus do not play well with each other.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.