Virus War
Game logic
Game Logic code
import random
MOVES_PER_TURN = 3
def init_game(game_settings):
size = game_settings.get('Size')
if size == 'Any':
size = random.choice(['7x7', '11x11'])
if size == '7x7':
width = 7
height = 7
initial_virus_positions = [(1, 1), (5, 5)]
elif size == '11x11':
width = 11
height = 11
initial_virus_positions = [(2, 2), (8, 8)]
viruses = [
{
'x': initial_virus_positions[0][0],
'y': initial_virus_positions[0][1],
'player_idx': 0,
'is_zombie': False
},
{
'x': initial_virus_positions[1][0],
'y': initial_virus_positions[1][1],
'player_idx': 1,
'is_zombie': False
}]
state = {
'player1_required_move_count': 1,
'player2_required_move_count': 0,
'viruses': viruses,
'player_turn_idx': 0,
'spots_visible_to_player1': __get_visible_spots(viruses, 0, width, height),
'spots_visible_to_player2': __get_visible_spots(viruses, 1, width, height),
'board_width': width,
'board_height': height
}
return state
def move(state, x, y):
if x < 0 or x >= state["board_width"] or y < 0 or y >= state["board_height"]:
raise InvalidActionError("Input out of bound.")
if state['player_turn_idx'] == 0:
required_move_count = state['player1_required_move_count']
spots_visible = state['spots_visible_to_player1']
else:
required_move_count = state['player2_required_move_count']
spots_visible = state['spots_visible_to_player2']
found = False
for v in spots_visible:
if x == v['x'] and y == v['y']:
found = True
break
if not found:
raise InvalidActionError("This is not a visible spot you can move to.")
player_idx = state['player_turn_idx']
new_viruses = []
is_new_virus = True
for v in state['viruses']:
if x == v['x'] and y == v['y']:
is_new_virus = False
if v['is_zombie']:
raise InvalidActionError("This is overlapping with a zombie virus.")
if v['player_idx'] == player_idx:
raise InvalidActionError( "This is overlapping with your own live virus.")
else:
new_viruses.append(
{
'x': x,
'y': y,
'player_idx': player_idx,
'is_zombie': True
})
else:
new_viruses.append(v)
if is_new_virus:
new_viruses.append({
'x': x,
'y': y,
'player_idx': player_idx,
'is_zombie': False
})
state['viruses'] = new_viruses
state['spots_visible_to_player1'] = __get_visible_spots(
state['viruses'], 0, state['board_width'], state['board_height'])
state['spots_visible_to_player2'] = __get_visible_spots(
state['viruses'], 1, state['board_width'], state['board_height'])
if player_idx == 0:
if state['player1_required_move_count'] == 1:
state['player2_required_move_count'] = MOVES_PER_TURN
state['player_turn_idx'] = 1
state['player1_required_move_count'] = state['player1_required_move_count'] - 1
else:
if state['player2_required_move_count'] == 1:
state['player1_required_move_count'] = MOVES_PER_TURN
state['player_turn_idx'] = 0
state['player2_required_move_count'] = state['player2_required_move_count'] - 1
return state
def __get_visible_spots(viruses, player_idx, board_width, board_height):
"""
Only spots that are adjacent to live viruses, or spots that are adjacent to zombie viruses that are connected to live viruses via other zombie viruses are visible.
Zombie viruses themselves are also visible, even if they aren't connected.
Return those spots.
"""
position_to_virus_map = {}
positions_connected_to_live = set()
zombie_positions = []
for v in viruses:
position = (v['x'], v['y'])
position_to_virus_map[position] = v
if v['player_idx'] == player_idx:
if v['is_zombie']:
zombie_positions.append(position)
else:
positions_connected_to_live.add(position)
connected_zombies = __get_zombies_connected_to_live_helper(positions_connected_to_live, zombie_positions)
for cz in connected_zombies:
positions_connected_to_live.add(cz)
visible_spots = set()
for x, y in positions_connected_to_live:
for offset_x in range(-1, 2):
for offset_y in range(-1, 2):
new_x = x + offset_x
new_y = y + offset_y
if new_x < 0 or new_x >= board_width or new_y < 0 or new_y >= board_height:
continue
visible_spots.add((new_x, new_y))
for x, y in zombie_positions:
visible_spots.add((x, y))
# Convert visible spots into virus model. Empty spots without a virus will be represented as a virus with player_idx = -1.
visible_viruses = []
for p in visible_spots:
if p in position_to_virus_map:
visible_viruses.append(position_to_virus_map[p])
else:
visible_viruses.append({
'x': p[0],
'y': p[1],
'player_idx': -1,
'is_zombie': False
})
return visible_viruses
def __get_zombies_connected_to_live_helper(positions_connected_to_live, zombie_positions):
seen = set()
zombies_connected = set()
for p in zombie_positions:
if p in seen:
continue
# A DFS for each zombie group to see if they are connected to live viruses at some point
current_zombie_group = set([p])
is_group_connected_to_live = False
stack = [p]
while stack:
current = stack.pop()
seen.add(current)
if current in positions_connected_to_live:
is_group_connected_to_live = True
continue
current_zombie_group.add(current)
for offset_x in range(-1, 2):
for offset_y in range(-1, 2):
neighbor = (current[0] + offset_x, current[1] + offset_y)
if neighbor in seen or (neighbor not in positions_connected_to_live and neighbor not in zombie_positions):
continue
stack.append(neighbor)
if is_group_connected_to_live:
for z in current_zombie_group:
zombies_connected.add(z)
return zombies_connected
def get_game_result(state):
if state['player1_required_move_count'] > 0:
for v in state['spots_visible_to_player1']:
if v['player_idx'] == -1 or (v['player_idx'] == 1 and not v['is_zombie']):
return {
"game_result": "NoWinnerYet"
}
return {
"game_result": "Winner",
"winner_idx": 1
}
else:
for v in state['spots_visible_to_player2']:
if v['player_idx'] == -1 or (v['player_idx'] == 0 and not v['is_zombie']):
return {
"game_result": "NoWinnerYet"
}
return {
"game_result": "Winner",
"winner_idx": 0
}
def get_player_states(state):
player_states = []
for i in range(2):
if i == 0:
required_move_count = state['player1_required_move_count']
visible_viruses = state['spots_visible_to_player1']
else:
required_move_count = state['player2_required_move_count']
visible_viruses = state['spots_visible_to_player2']
player_states.append({
'player_turn_idx': state['player_turn_idx'],
'required_move_count': required_move_count,
'visible_viruses': visible_viruses,
'board_width': state['board_width'],
'board_height': state['board_height']
})
return player_states
Game state schema
message State {
// Required field to indicate the player who should be making the next move
// Values = 0 or 1
int32 player_turn_idx = 1;
// With the exception of the first move, players alternate taking 3 consecutive
// moves. This field indicates how many moves are still required before switching
// to the opponent. Player who cannot make the move loses.
int32 required_move_count = 2;
// A list of visible spots that a player can potentially take an action on.
// Pick an empty spot to move to, or convert opponent's live virus into a zombie.
repeated Virus visible_viruses = 3;
// Width of board, configured by the game setting
int32 board_width = 4;
// Height of board, configured by the game setting
int32 board_height = 5;
}
/*
Describes the status of a spot on the grid. A player may not see the complete
board.
*/
message Virus {
// Coordinate on the grid
int32 x = 1;
int32 y = 2;
// Can be -1, 0, or 1. If the board is empty at this coordinate, player_idx is -1.
int32 player_idx = 3;
// A virus can be alive or zombie. If the coordinate is empty, this field is false.
bool is_zombie = 4;
}
Action schema
move(int x, int y)
Last updated