Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/mideind/Netskrafl
Browse files Browse the repository at this point in the history
  • Loading branch information
vthorsteinsson committed Jan 25, 2021
2 parents d218ee6 + 89ccf5f commit ebbb344
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 113 deletions.
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ resources/client_secret.txt
# Private GAE services key
resources/netskrafl-*.json

# Unsorted BIN word lists
resources/ordalistimax15.txt
resources/ordalisti.full.sorted.txt
resources/ordalisti.mid.sorted.txt

# Non-Icelandic word lists
resources/TWL06.txt
resources/sowpods.txt

# CSS files generated from Less
static/skrafl-curry.css
static/skrafl-desat.css
static/skrafl-*.css

# Concatenated JS
static/netskrafl.js
Expand Down
14 changes: 7 additions & 7 deletions admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import logging
from threading import Thread
from datetime import datetime

from flask import jsonify
from flask import request
Expand Down Expand Up @@ -126,37 +127,36 @@ def admin_loadgame() -> str:
uuid = request.form.get("uuid", None)
game = None

g: Optional[Dict[str, Any]]
g: Optional[Dict[str, Any]] = None

if uuid:
# Attempt to load the game whose id is in the URL query string
game = Game.load(uuid)

if game:
if game is not None and game.state is not None:
board = game.state.board()
now = datetime.utcnow()
g = dict(
uuid=game.uuid,
timestamp=Alphabet.format_timestamp(game.timestamp),
timestamp=Alphabet.format_timestamp(game.timestamp or now),
player0=game.player_ids[0],
player1=game.player_ids[1],
robot_level=game.robot_level,
ts_last_move=Alphabet.format_timestamp(game.ts_last_move),
ts_last_move=Alphabet.format_timestamp(game.ts_last_move or now),
irack0=game.initial_racks[0],
irack1=game.initial_racks[1],
prefs=game._preferences,
over=game.is_over(),
moves=[
(
m.player,
m.move.summary(board),
m.move.summary(game.state),
m.rack,
Alphabet.format_timestamp(m.ts),
)
for m in game.moves
],
)
else:
g = None

return jsonify(game=g)

2 changes: 2 additions & 0 deletions dawgtester.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,12 @@ def run(self, fname, relpath):
self._test_false("mismri")
self._test_false("strinum")
self._test_false("sigrihrt")
self._test_false("býj")

self._test_true("fau")
self._test_true("ifa")
self._test_true("yla")
self._test_true("ritu")

# All two-letter words on the official list of the
# Icelandic Skrafl society
Expand Down
40 changes: 24 additions & 16 deletions netskrafl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,8 @@ def gamestate():
uuid = rq.get("game")

user_id = current_user_id()
assert user_id is not None

game = Game.load(uuid) if uuid else None

if game is None:
Expand Down Expand Up @@ -1567,7 +1569,7 @@ def chatmsg() -> Response:
if uuid:
game = Game.load(uuid)

if game is None or not game.has_player(user_id):
if game is None or user_id is None or not game.has_player(user_id):
# The logged-in user must be a player in the game
return jsonify(ok=False)

Expand All @@ -1584,8 +1586,11 @@ def chatmsg() -> Response:
msg = {}
for p in range(0, 2):
# Send a Firebase notification to /game/[gameid]/[userid]/chat
msg["game/" + uuid + "/" + game.player_id(p) + "/chat"] = md
firebase.send_message(msg)
pid = game.player_id(p)
if pid is not None:
msg["game/" + uuid + "/" + pid + "/chat"] = md
if msg:
firebase.send_message(msg)

return jsonify(ok=True)

Expand All @@ -1609,7 +1614,7 @@ def chatload() -> Response:
if uuid:
game = Game.load(uuid)

if game is None or not game.has_player(user_id):
if game is None or user_id is None or not game.has_player(user_id):
# The logged-in user must be a player in the game
return jsonify(ok=False)

Expand Down Expand Up @@ -1680,9 +1685,10 @@ def review() -> ResponseType:
# 19 is what fits on screen
best_moves = apl.generate_best_moves(19)

if game.has_player(user.id()):
uid = user.id()
if uid is not None and game.has_player(uid):
# Look at the game from the point of view of this player
user_index = game.player_index(user.id())
user_index = game.player_index(uid)
else:
# This is an outside spectator: look at it from the point of view of
# player 0, or the human player if player 0 is an autoplayer
Expand All @@ -1708,9 +1714,8 @@ def bestmoves() -> Response:
user = current_user()
assert user is not None

# !!! FIXME
if False: # not user.has_paid():
# User must be a paying friend
if not user.has_paid() and not running_local:
# User must be a paying friend, or we're on a development server
return jsonify(result=Error.USER_MUST_BE_FRIEND)

rq = RequestData(request)
Expand Down Expand Up @@ -1748,9 +1753,10 @@ def bestmoves() -> Response:
(player_index, m.summary(state)) for m, _ in apl.generate_best_moves(19)
]

if game.has_player(user.id()):
uid = user.id()
if uid is not None and game.has_player(uid):
# Look at the game from the point of view of this player
user_index = game.player_index(user.id())
user_index = game.player_index(uid)
else:
# This is an outside spectator: look at it from the point of view of
# player 0, or the human player if player 0 is an autoplayer
Expand Down Expand Up @@ -2243,11 +2249,13 @@ def board() -> ResponseType:
# Delete the Firebase subtree for this game,
# to get earlier move and chat notifications out of the way
if firebase_token is not None and user is not None:
msg = {
"game/" + game.id() + "/" + uid: None,
"user/" + uid + "/wait": None,
}
firebase.send_message(msg)
game_id = game.id()
if game_id is not None:
msg = {
"game/" + game_id + "/" + uid: None,
"user/" + uid + "/wait": None,
}
firebase.send_message(msg)
# No need to clear other stuff on the /user/[user_id]/ path,
# since we're not listening to it in board.html

Expand Down
8 changes: 8 additions & 0 deletions resources/ordalisti.add.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ ey
fa
fleiru
fleirum
Expand Down Expand Up @@ -112,6 +113,13 @@ pæ
re
rita
ritan
ritu
rituna
ritunnar
ritunni
riturnar
Expand Down
1 change: 1 addition & 0 deletions resources/ordalisti.remove.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,4 @@ blýlegrar
blýlegs
blýlega
blýlegur
býj
15 changes: 7 additions & 8 deletions skrafldb.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,16 +444,15 @@ class GameModel(ndb.Model):
player0 = ndb.KeyProperty(kind=UserModel)
player1 = ndb.KeyProperty(kind=UserModel)

# The racks
rack0 = ndb.StringProperty()
rack1 = ndb.StringProperty()
rack0 = ndb.StringProperty(indexed=False)
rack1 = ndb.StringProperty(indexed=False)

# The scores
score0 = ndb.IntegerProperty()
score1 = ndb.IntegerProperty()
score0 = ndb.IntegerProperty(indexed=False)
score1 = ndb.IntegerProperty(indexed=False)

# Whose turn is it next, 0 or 1?
to_move = ndb.IntegerProperty()
to_move = ndb.IntegerProperty(indexed=False)

# How difficult should the robot player be (if the opponent is a robot)?
# None or 0 = most difficult
Expand All @@ -472,8 +471,8 @@ class GameModel(ndb.Model):
moves = ndb.LocalStructuredProperty(MoveModel, repeated=True, indexed=False)

# The initial racks
irack0 = ndb.StringProperty(required=False, default=None)
irack1 = ndb.StringProperty(required=False, default=None)
irack0 = ndb.StringProperty(required=False, indexed=False, default=None)
irack1 = ndb.StringProperty(required=False, indexed=False, default=None)

# Game preferences, such as duration, alternative bags or boards, etc.
prefs = ndb.JsonProperty(required=False, default=None)
Expand Down
28 changes: 16 additions & 12 deletions skraflgame.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ def _make_new(
player1_id: Optional[str],
robot_level: int = 0,
prefs: Optional[PrefsDict] = None,
):
) -> None:
""" Initialize a new, fresh game """
self._preferences = prefs
# If either player0_id or player1_id is None, this is a human-vs-autoplayer game
Expand All @@ -719,7 +719,7 @@ def new(
player1_id: Optional[str],
robot_level: int = 0,
prefs: Optional[PrefsDict] = None,
):
) -> Game:
""" Start and initialize a new game """
game = cls(Unique.id()) # Assign a new unique id to the game
if randint(0, 1) == 1:
Expand All @@ -734,20 +734,20 @@ def new(
return game

@classmethod
def load(cls, uuid, use_cache=True):
def load(cls, uuid: str, use_cache: bool = True) -> Optional[Game]:
""" Load an already existing game from persistent storage """
with Game._lock:
# Ensure that the game load does not introduce race conditions
return cls._load_locked(uuid, use_cache)

def store(self):
def store(self) -> None:
""" Store the game state in persistent storage """
# Avoid race conditions by securing the lock before storing
with Game._lock:
self._store_locked()

@classmethod
def _load_locked(cls, uuid, use_cache=True):
def _load_locked(cls, uuid: str, use_cache: bool = True) -> Optional[Game]:
""" Load an existing game from cache or persistent storage under lock """

gm = GameModel.fetch(uuid, use_cache)
Expand Down Expand Up @@ -848,7 +848,7 @@ def _load_locked(cls, uuid, use_cache=True):
elif mm.tiles == "RESP":

# Response to challenge
m = ResponseMove()
m = ResponseMove(mm.score)

if m is None:
# Something is wrong: mark the game as erroneous
Expand Down Expand Up @@ -882,7 +882,7 @@ def _load_locked(cls, uuid, use_cache=True):

return game

def _store_locked(self):
def _store_locked(self) -> None:
""" Store the game after having acquired the object lock """

assert self.uuid is not None
Expand Down Expand Up @@ -1113,7 +1113,7 @@ def net_moves(self) -> List[MoveTuple]:
assert self.state is not None
net_m: List[MoveTuple] = []
for m in self.moves:
if isinstance(m.move, ResponseMove) and m.move.score(self.state) < 0:
if m.move.is_successful_challenge(self.state):
# Successful challenge: Erase the two previous moves
# (the challenge and the illegal move)
assert len(net_m) >= 2
Expand Down Expand Up @@ -1473,13 +1473,17 @@ def is_last_challenge(self) -> bool:
return self.state.is_last_challenge()

def client_state(
self, player_index: int, lastmove: Optional[MoveBase] = None, deep: bool = False
self,
player_index: Optional[int],
lastmove: Optional[MoveBase] = None,
deep: bool = False,
) -> Dict[str, Any]:
""" Create a package of information for the client about the current state """
assert self.state is not None
reply: Dict[str, Any] = dict()
num_moves = 1
lm = None
lm: Optional[MoveBase] = None
succ_chall = False
if self.last_move is not None:
# Show the autoplayer or response move that was made
lm = self.last_move
Expand All @@ -1490,8 +1494,8 @@ def client_state(
lm = lastmove
if lm is not None:
reply["lastmove"] = lm.details(self.state)
# Successful challenge?
succ_chall = isinstance(lm, ResponseMove) and lm.score(self.state) < 0
# Successful challenge?
succ_chall = lm.is_successful_challenge(self.state)
newmoves = [
(m.player, m.move.summary(self.state)) for m in self.moves[-num_moves:]
]
Expand Down
Loading

0 comments on commit ebbb344

Please sign in to comment.