Переглянути джерело

basic bot example kinda works

Alexey Dorokhov 3 роки тому
батько
коміт
be418a16d7
5 змінених файлів з 395 додано та 75 видалено
  1. 117 22
      bot_example.py
  2. 114 46
      competitive_tetris.py
  3. 13 1
      wire_protocol.fbs
  4. 6 6
      wire_protocol/CommandResponse.py
  5. 145 0
      wire_protocol/GameState.py

+ 117 - 22
bot_example.py

@@ -15,23 +15,82 @@ import wire_protocol.CommandRequest
 import wire_protocol.CommandResponse
 import wire_protocol.CommandResponseError
 
+import game_state
+
+def dummy_next_piece_func():
+    return 0
+
+def cell_strength(field, row, col):
+    neighbour_count = 0
+    for r in range(row-1, row+2):
+        for c in range(col-1, col+2):
+            if (r < 0 or r >= field.shape[0] or
+                c < 0 or c >= field.shape[1]):
+                # walls are neighbours
+                neighbour_count += 1
+            elif field[r][c] > 0:
+                neighbour_count += 1
+    if field[row][col]:
+        return neighbour_count * row
+    else:
+        return -neighbour_count * row
+
+
+def field_heuristic(field):
+    result = 0.0
+    for r in range(field.shape[0]):
+        for c in range(field.shape[1]):
+            result += cell_strength(field, r, c)
+    return result
+
+
+class ShiftAndRotate:
+    def __init__(self, rotate_count, col_offset, value, state):
+        self.rotate_count = rotate_count
+        self.col_offset = col_offset
+        self.value = value
+        self.state = state
+
+
+def create_shifts_and_rotates(state):
+    shift_and_rotates = []
+
+    for rotate_count in range(5):
+        rotate_state = state
+        for _ in range(rotate_count):
+            rotate_state = rotate_state.rotate()
+            if rotate_state is None:
+                break
+
+        if rotate_state is None:
+            continue
+
+        for col_offset in range(-game_state.FIELD_WIDTH, game_state.FIELD_HEIGHT+1):
+            next_state = rotate_state.shift(col_offset)
+            if next_state is not None:
+                final_state, is_running = next_state.land().advance()
+                if is_running:
+                    value = (field_heuristic(final_state.data.grid) +
+                             final_state.data.score*10000.0)
+                    shift_and_rotates.append(ShiftAndRotate(
+                            rotate_count,
+                            col_offset,
+                            value,
+                            final_state))
+    return shift_and_rotates
+
+
 def main():
     context = zmq.Context()
     socket = context.socket(zmq.REQ)
-    res = socket.connect("tcp://localhost:5555")
-    print(f"connected: {res}")
+    socket.connect("tcp://localhost:5555")
 
     while True:
         builder = flatbuffers.Builder()
         wire_protocol.CommandRequest.Start(builder)
         wire_protocol.CommandRequest.AddCommand(
                 builder,
-                random.choice([
-                    wire_protocol.Command.Command.ShiftLeft,
-                    wire_protocol.Command.Command.ShiftRight,
-                    wire_protocol.Command.Command.Rotate,
-                    #wire_protocol.Command.Command.Land,
-                    ]))
+                wire_protocol.Command.Command.GetGameState)
         req = wire_protocol.CommandRequest.End(builder)
         builder.Finish(req)
         socket.send(builder.Output())
@@ -39,23 +98,59 @@ def main():
         rsp_buf = socket.recv()
         rsp = wire_protocol.CommandResponse.CommandResponse.GetRootAs(rsp_buf)
 
-        builder = flatbuffers.Builder()
-        wire_protocol.CommandRequest.Start(builder)
-        wire_protocol.CommandRequest.AddCommand(
-                builder,
-                wire_protocol.Command.Command.GetGameState)
-        req = wire_protocol.CommandRequest.End(builder)
-        builder.Finish(req)
-        socket.send(builder.Output())
+        grid = rsp.GameState().GridAsNumpy()
+        grid = grid.reshape((rsp.GameState().Rows(), rsp.GameState().Cols()))
 
-        rsp_buf = socket.recv()
-        rsp = wire_protocol.CommandResponse.CommandResponse.GetRootAs(rsp_buf)
+        state = game_state.GameState()
+        state.data = game_state.GameStateData(
+                grid = grid,
+                current_piece = rsp.GameState().CurrentPiece(),
+                next_piece = rsp.GameState().NextPiece(),
+                rotation = rsp.GameState().PieceRotation(),
+                row = rsp.GameState().PieceRow(),
+                col = rsp.GameState().PieceCol(),
+                score = rsp.GameState().Score())
+        state.next_piece_func = dummy_next_piece_func
+
+
+        next_moves = create_shifts_and_rotates(state)
+        if len(next_moves) > 0:
+            best_moves = sorted(next_moves, key=lambda x: x.value, reverse=True)
+            best_move = best_moves[0]
+
+            command = wire_protocol.Command.Command.Rotate
+            for _ in range(best_move.rotate_count):
+                builder = flatbuffers.Builder()
+                wire_protocol.CommandRequest.Start(builder)
+                wire_protocol.CommandRequest.AddCommand(builder, command)
+                req = wire_protocol.CommandRequest.End(builder)
+                builder.Finish(req)
+                socket.send(builder.Output())
+                rsp_buf = socket.recv()
+                rsp = wire_protocol.CommandResponse.CommandResponse.GetRootAs(rsp_buf)
 
-        cells = rsp.Playfield().CellsAsNumpy()
-        cells = cells.reshape((rsp.Playfield().Rows(), rsp.Playfield().Cols()))
-        print(f"cells = \n{cells}\n")
-        time.sleep(0.24)
+            command = wire_protocol.Command.Command.ShiftRight
+            if best_move.col_offset < 0:
+                command = wire_protocol.Command.Command.ShiftLeft
+            for _ in range(abs(best_move.col_offset)):
+                builder = flatbuffers.Builder()
+                wire_protocol.CommandRequest.Start(builder)
+                wire_protocol.CommandRequest.AddCommand(builder, command)
+                req = wire_protocol.CommandRequest.End(builder)
+                builder.Finish(req)
+                socket.send(builder.Output())
+                rsp_buf = socket.recv()
+                rsp = wire_protocol.CommandResponse.CommandResponse.GetRootAs(rsp_buf)
 
+            builder = flatbuffers.Builder()
+            wire_protocol.CommandRequest.Start(builder)
+            wire_protocol.CommandRequest.AddCommand(
+                    builder, wire_protocol.Command.Command.Land)
+            req = wire_protocol.CommandRequest.End(builder)
+            builder.Finish(req)
+            socket.send(builder.Output())
+            rsp_buf = socket.recv()
+            rsp = wire_protocol.CommandResponse.CommandResponse.GetRootAs(rsp_buf)
 
 if __name__ == "__main__":
     main()

+ 114 - 46
competitive_tetris.py

@@ -15,6 +15,7 @@ import wire_protocol.CommandRequest
 import wire_protocol.CommandResponse
 import wire_protocol.Playfield
 import wire_protocol.CommandResponseError
+import wire_protocol.GameState
 
 pygame.font.init()
 
@@ -104,6 +105,111 @@ def draw_game_state_to_window(surface, state):
     draw_grid(surface, field)
 
 
+def handle_shift_left(state):
+    error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
+    next_state = state.shift(-1)
+    if next_state is None:
+        error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
+    else:
+        state = next_state
+
+    builder = flatbuffers.Builder()
+    wire_protocol.CommandResponse.Start(builder)
+    wire_protocol.CommandResponse.AddError(builder, error_code)
+    rsp = wire_protocol.CommandResponse.End(builder)
+    builder.Finish(rsp)
+    return state, builder
+
+
+def handle_shift_right(state):
+    error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
+    next_state = state.shift(1)
+    if next_state is None:
+        error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
+    else:
+        state = next_state
+
+    builder = flatbuffers.Builder()
+    wire_protocol.CommandResponse.Start(builder)
+    wire_protocol.CommandResponse.AddError(builder, error_code)
+    rsp = wire_protocol.CommandResponse.End(builder)
+    builder.Finish(rsp)
+    return state, builder
+
+
+def handle_rotate(state):
+    error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
+    next_state = state.rotate()
+    if next_state is None:
+        error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
+    else:
+        state = next_state
+
+    builder = flatbuffers.Builder()
+    wire_protocol.CommandResponse.Start(builder)
+    wire_protocol.CommandResponse.AddError(builder, error_code)
+    rsp = wire_protocol.CommandResponse.End(builder)
+    builder.Finish(rsp)
+    return state, builder
+
+
+def handle_land(state):
+    error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
+    next_state = state.land()
+    if next_state is None:
+        error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
+    else:
+        state = next_state
+
+    builder = flatbuffers.Builder()
+    wire_protocol.CommandResponse.Start(builder)
+    wire_protocol.CommandResponse.AddError(builder, error_code)
+    rsp = wire_protocol.CommandResponse.End(builder)
+    builder.Finish(rsp)
+    return state, builder
+
+
+def handle_other(state):
+    builder = flatbuffers.Builder()
+    wire_protocol.CommandResponse.Start(builder)
+    wire_protocol.CommandResponse.AddError(
+            builder,
+            wire_protocol.CommandResponseError.CommandResponseError.UNKNOWN_COMMAND)
+    rsp = wire_protocol.CommandResponse.End(builder)
+    builder.Finish(rsp)
+    return state, builder
+
+
+def handle_get_game_state(state):
+    error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
+    builder = flatbuffers.Builder()
+
+    wire_protocol.GameState.StartGridVector(builder, state.data.grid.size)
+    builder.head -= state.data.grid.size
+    builder.Bytes[builder.head:(builder.head + state.data.grid.size)] = state.data.grid.tobytes()
+    grid_vector = builder.EndVector(state.data.grid.size)
+
+    wire_protocol.GameState.Start(builder)
+    wire_protocol.GameState.AddRows(builder, state.data.grid.shape[0])
+    wire_protocol.GameState.AddCols(builder, state.data.grid.shape[1])
+    wire_protocol.GameState.AddNextPiece(builder, state.data.next_piece)
+    wire_protocol.GameState.AddCurrentPiece(builder, state.data.current_piece)
+    wire_protocol.GameState.AddPieceRotation(builder, state.data.rotation)
+    wire_protocol.GameState.AddPieceRow(builder, state.data.row)
+    wire_protocol.GameState.AddPieceCol(builder, state.data.col)
+    wire_protocol.GameState.AddScore(builder, state.data.score)
+    wire_protocol.GameState.AddGrid(builder, grid_vector)
+    game_state_msg = wire_protocol.GameState.End(builder)
+
+    wire_protocol.CommandResponse.Start(builder)
+    wire_protocol.CommandResponse.AddError(builder, error_code)
+    wire_protocol.CommandResponse.AddGameState(builder, game_state_msg)
+    rsp = wire_protocol.CommandResponse.End(builder)
+    builder.Finish(rsp)
+
+    return state, builder
+
+
 def main(win):
     run = True
     frame_time = 0
@@ -132,59 +238,21 @@ def main(win):
         for sock, _ in poller.poll(timeout=0):
             req_buf = sock.recv()
             req = wire_protocol.CommandRequest.CommandRequest.GetRootAs(req_buf)
-
-            error_code = wire_protocol.CommandResponseError.CommandResponseError.OK
-            cells = None
+            builder = None
 
             if req.Command() == wire_protocol.Command.Command.ShiftLeft:
-                next_state = state.shift(-1)
-                if next_state is None:
-                    error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
-                else:
-                    state = next_state
+                state, builder = handle_shift_left(state)
             elif req.Command() == wire_protocol.Command.Command.ShiftRight:
-                next_state = state.shift(1)
-                if next_state is None:
-                    error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
-                else:
-                    state = next_state
+                state, builder = handle_shift_right(state)
             elif req.Command() == wire_protocol.Command.Command.Rotate:
-                next_state = state.rotate()
-                if next_state is None:
-                    error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
-                else:
-                    state = next_state
+                state, builder = handle_rotate(state)
             elif req.Command() == wire_protocol.Command.Command.Land:
-                next_state = state.land()
-                if next_state is None:
-                    error_code = wire_protocol.CommandResponseError.CommandResponseError.INVALID_COMMAND
-                else:
-                    state = next_state
+                state, builder = handle_land(state)
             elif req.Command() == wire_protocol.Command.Command.GetGameState:
-                cells = state.get_field()
+                state, builder = handle_get_game_state(state)
             else:
-                error_code = wire_protocol.CommandResponseError.CommandResponseError.UNKNOWN_COMMAND
-
-            builder = flatbuffers.Builder()
-            playfield = None
-            if cells is not None:
-                wire_protocol.Playfield.StartCellsVector(builder, cells.size)
-                builder.head -= cells.size
-                builder.Bytes[builder.head:(builder.head + cells.size)] = cells.tobytes()
-                cells_vector = builder.EndVector(cells.size)
-
-                wire_protocol.Playfield.Start(builder)
-                wire_protocol.Playfield.AddRows(builder, cells.shape[0])
-                wire_protocol.Playfield.AddCols(builder, cells.shape[1])
-                wire_protocol.Playfield.AddCells(builder, cells_vector)
-                playfield = wire_protocol.Playfield.End(builder)
-
-            wire_protocol.CommandResponse.Start(builder)
-            wire_protocol.CommandResponse.AddError(builder, error_code)
-            if playfield is not None:
-                wire_protocol.CommandResponse.AddPlayfield(builder, playfield)
-            rsp = wire_protocol.CommandResponse.End(builder)
-            builder.Finish(rsp)
+                state, builder = handle_other(state)
+
             socket.send(builder.Output())
 
         is_game_running = True

+ 13 - 1
wire_protocol.fbs

@@ -24,9 +24,21 @@ table Playfield {
   cells:[ubyte];
 }
 
+table GameState {
+  rows:short;
+  cols:short;
+  next_piece:short;
+  current_piece:short;
+  piece_rotation:short;
+  piece_row:short;
+  piece_col:short;
+  score:short;
+  grid:[ubyte];
+}
+
 table CommandResponse {
   error:CommandResponseError;
-  playfield:Playfield;
+  game_state:GameState;
 }
 
 root_type CommandRequest;

+ 6 - 6
wire_protocol/CommandResponse.py

@@ -32,12 +32,12 @@ class CommandResponse(object):
         return 0
 
     # CommandResponse
-    def Playfield(self):
+    def GameState(self):
         o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
         if o != 0:
             x = self._tab.Indirect(o + self._tab.Pos)
-            from wire_protocol.Playfield import Playfield
-            obj = Playfield()
+            from wire_protocol.GameState import GameState
+            obj = GameState()
             obj.Init(self._tab.Bytes, x)
             return obj
         return None
@@ -48,9 +48,9 @@ def Start(builder):
 def CommandResponseAddError(builder, error): builder.PrependInt16Slot(0, error, 0)
 def AddError(builder, error):
     return CommandResponseAddError(builder, error)
-def CommandResponseAddPlayfield(builder, playfield): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(playfield), 0)
-def AddPlayfield(builder, playfield):
-    return CommandResponseAddPlayfield(builder, playfield)
+def CommandResponseAddGameState(builder, gameState): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(gameState), 0)
+def AddGameState(builder, gameState):
+    return CommandResponseAddGameState(builder, gameState)
 def CommandResponseEnd(builder): return builder.EndObject()
 def End(builder):
     return CommandResponseEnd(builder)

+ 145 - 0
wire_protocol/GameState.py

@@ -0,0 +1,145 @@
+# automatically generated by the FlatBuffers compiler, do not modify
+
+# namespace: wire_protocol
+
+import flatbuffers
+from flatbuffers.compat import import_numpy
+np = import_numpy()
+
+class GameState(object):
+    __slots__ = ['_tab']
+
+    @classmethod
+    def GetRootAs(cls, buf, offset=0):
+        n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
+        x = GameState()
+        x.Init(buf, n + offset)
+        return x
+
+    @classmethod
+    def GetRootAsGameState(cls, buf, offset=0):
+        """This method is deprecated. Please switch to GetRootAs."""
+        return cls.GetRootAs(buf, offset)
+    # GameState
+    def Init(self, buf, pos):
+        self._tab = flatbuffers.table.Table(buf, pos)
+
+    # GameState
+    def Rows(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
+        if o != 0:
+            return self._tab.Get(flatbuffers.number_types.Int16Flags, o + self._tab.Pos)
+        return 0
+
+    # GameState
+    def Cols(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
+        if o != 0:
+            return self._tab.Get(flatbuffers.number_types.Int16Flags, o + self._tab.Pos)
+        return 0
+
+    # GameState
+    def NextPiece(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
+        if o != 0:
+            return self._tab.Get(flatbuffers.number_types.Int16Flags, o + self._tab.Pos)
+        return 0
+
+    # GameState
+    def CurrentPiece(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
+        if o != 0:
+            return self._tab.Get(flatbuffers.number_types.Int16Flags, o + self._tab.Pos)
+        return 0
+
+    # GameState
+    def PieceRotation(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
+        if o != 0:
+            return self._tab.Get(flatbuffers.number_types.Int16Flags, o + self._tab.Pos)
+        return 0
+
+    # GameState
+    def PieceRow(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
+        if o != 0:
+            return self._tab.Get(flatbuffers.number_types.Int16Flags, o + self._tab.Pos)
+        return 0
+
+    # GameState
+    def PieceCol(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
+        if o != 0:
+            return self._tab.Get(flatbuffers.number_types.Int16Flags, o + self._tab.Pos)
+        return 0
+
+    # GameState
+    def Score(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
+        if o != 0:
+            return self._tab.Get(flatbuffers.number_types.Int16Flags, o + self._tab.Pos)
+        return 0
+
+    # GameState
+    def Grid(self, j):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
+        if o != 0:
+            a = self._tab.Vector(o)
+            return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
+        return 0
+
+    # GameState
+    def GridAsNumpy(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
+        if o != 0:
+            return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
+        return 0
+
+    # GameState
+    def GridLength(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
+        if o != 0:
+            return self._tab.VectorLen(o)
+        return 0
+
+    # GameState
+    def GridIsNone(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
+        return o == 0
+
+def GameStateStart(builder): builder.StartObject(9)
+def Start(builder):
+    return GameStateStart(builder)
+def GameStateAddRows(builder, rows): builder.PrependInt16Slot(0, rows, 0)
+def AddRows(builder, rows):
+    return GameStateAddRows(builder, rows)
+def GameStateAddCols(builder, cols): builder.PrependInt16Slot(1, cols, 0)
+def AddCols(builder, cols):
+    return GameStateAddCols(builder, cols)
+def GameStateAddNextPiece(builder, nextPiece): builder.PrependInt16Slot(2, nextPiece, 0)
+def AddNextPiece(builder, nextPiece):
+    return GameStateAddNextPiece(builder, nextPiece)
+def GameStateAddCurrentPiece(builder, currentPiece): builder.PrependInt16Slot(3, currentPiece, 0)
+def AddCurrentPiece(builder, currentPiece):
+    return GameStateAddCurrentPiece(builder, currentPiece)
+def GameStateAddPieceRotation(builder, pieceRotation): builder.PrependInt16Slot(4, pieceRotation, 0)
+def AddPieceRotation(builder, pieceRotation):
+    return GameStateAddPieceRotation(builder, pieceRotation)
+def GameStateAddPieceRow(builder, pieceRow): builder.PrependInt16Slot(5, pieceRow, 0)
+def AddPieceRow(builder, pieceRow):
+    return GameStateAddPieceRow(builder, pieceRow)
+def GameStateAddPieceCol(builder, pieceCol): builder.PrependInt16Slot(6, pieceCol, 0)
+def AddPieceCol(builder, pieceCol):
+    return GameStateAddPieceCol(builder, pieceCol)
+def GameStateAddScore(builder, score): builder.PrependInt16Slot(7, score, 0)
+def AddScore(builder, score):
+    return GameStateAddScore(builder, score)
+def GameStateAddGrid(builder, grid): builder.PrependUOffsetTRelativeSlot(8, flatbuffers.number_types.UOffsetTFlags.py_type(grid), 0)
+def AddGrid(builder, grid):
+    return GameStateAddGrid(builder, grid)
+def GameStateStartGridVector(builder, numElems): return builder.StartVector(1, numElems, 1)
+def StartGridVector(builder, numElems):
+    return GameStateStartGridVector(builder, numElems)
+def GameStateEnd(builder): return builder.EndObject()
+def End(builder):
+    return GameStateEnd(builder)