Mert Us Matthew Hannay Logan Starr
Dependencies: mbed 4DGL-uLCD-SE
Diff: main.cpp
- Revision:
- 14:f390d08e5f92
- Parent:
- 11:43c89579ac52
- Child:
- 15:cae96e8b62a3
- Child:
- 16:b66a720631dd
--- a/main.cpp Thu Dec 01 05:53:57 2022 +0000 +++ b/main.cpp Sun Dec 04 09:32:07 2022 +0000 @@ -4,6 +4,9 @@ uLCD_4DGL uLCD(p28,p27,p30); // serial tx, serial rx, reset pin; +DigitalOut myLed(LED1); +Serial Blue(p13, p14); + enum Piece {e, wK, bK, wQ, bQ, wR, bR, wB, bB, wN, bN, w, b}; std::vector<Piece> whitePieces; std::vector<Piece> blackPieces; @@ -81,15 +84,123 @@ return _pins.read(); } +const float KING_POSITION_VALUES[] = {2.0, 3.0, 1.0, 0.0, 0.0, 1.0, 3.0, 2,0, + 2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 2.0, 2.0, + -1.0, -2.0, -2.0, -2.0, -2.0, -2.0, -2.0, -1.0, + -2.0, -3.0, -3.0, -4.0, -4.0, -3.0, -3.0, -2,0, + -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0, + -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0, + -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0, + -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0}; + +const float QUEEN_POSITION_VALUES[] = {-2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0, + -1.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, -1.0, + -1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0, + 0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5, + -0.5, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5, + -1.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0, + -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, + -2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0}; + +const float ROOK_POSITION_VALUES[] = {0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, + -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, + -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, + -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, + -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, + -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, + 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + +const float BISHOP_POSITION_VALUES[] = {-2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0, + -1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, -1.0, + -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, + -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, -1.0, + -1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, -1.0, + -1.0, 0.0, 0.5, 1.0, 1.0, 0.5, 0.0, -1.0, + -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, + -2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0}; +const float KNIGHT_POSITION_VALUES[] = {-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0, + -4.0, -2.0, 0.0, 0.5, 0.5, 0.0, -2.0, -4.0, + -3.0, 0.5, 1.0, 1.5, 1.5, 1.0, 0.5, -3.0, + -3.0, 0.0, 1.5, 2.0, 2.0, 1.5, 0.0, -3.0, + -3.0, 0.5, 1.5, 2.0, 2.0, 1.5, 0.5, -3.0, + -3.0, 0.0, 1.0, 1.5, 1.5, 1.0, 0.0, -3.0, + -4.0, -2.0, 0.0, 0.0, 0.0, 0.0, -2.0, -4.0, + -5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0}; +const float PAWN_POSITION_VALUES[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.5, 1.0, 1.0, -2.0, -2.0, 1.0, 1.0, 0.5, + 0.5, -0.5, -1.0, 0.0, 0.0, -1.0, -0.5, 0.5, + 0.0, 0.0, 0.0, 2.0, 2.0, 0.0, 0.0, 0.0, + 0.5, 0.5, 1.0, 2.5, 2.5, 1.0, 0.5, 0.5, + 1.0, 1.0, 2.0, 3.0, 3.0, 2.0, 1.0, 1.0, + 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + class BoardState { private: Piece array[64]; + // These values are for white pieces, need to be inverted + // when making calculations for black pieces. + static const float PAWN_VALUE = 10.0; + static const float KNIGHT_VALUE = 30.0; + static const float BISHOP_VALUE = 30.0; + static const float ROOK_VALUE = 50.0; + static const float QUEEN_VALUE = 90.0; + static const float KING_VALUE = 900.0; public: + BoardState() {} + // calculates the advantage difference for the board state float calculateBoardState() { - return 0.0; + float sum = 0.0; + for (int i = 0; i < 64; i++) { + int row = i / 8; + int column = i % 8; + Piece curr = getPiece(row, column); + switch(curr) { + case wK: + sum += KING_VALUE + KING_POSITION_VALUES[row*8 + column]; + break; + case bK: + sum -= (KING_VALUE + KING_POSITION_VALUES[(7-row)*8 + (7-column)]); + break; + case wQ: + sum += QUEEN_VALUE + QUEEN_POSITION_VALUES[row*8 + column]; + break; + case bQ: + sum -= (QUEEN_VALUE - QUEEN_POSITION_VALUES[(7-row)*8 + (7-column)]); + break; + case wR: + sum += ROOK_VALUE + ROOK_POSITION_VALUES[row*8 + column]; + break; + case bR: + sum -= (ROOK_VALUE - ROOK_POSITION_VALUES[(7-row)*8 + (7-column)]); + break; + case wB: + sum += BISHOP_VALUE + BISHOP_POSITION_VALUES[row*8 + column]; + break; + case bB: + sum -= (BISHOP_VALUE - BISHOP_POSITION_VALUES[(7-row)*8 + (7-column)]); + break; + case wN: + sum += KNIGHT_VALUE + KNIGHT_POSITION_VALUES[row*8 + column]; + break; + case bN: + sum -= (KNIGHT_VALUE - KNIGHT_POSITION_VALUES[(7-row)*8 + (7-column)]); + break; + case w: + sum += PAWN_VALUE + PAWN_POSITION_VALUES[row*8 + column]; + break; + case b: + sum -= (PAWN_VALUE - PAWN_POSITION_VALUES[(7-row)*8 + (7-column)]); + break; + default: + break; + } + } + return sum; } // returns the piece at a given location @@ -144,44 +255,204 @@ case bK: isWhite = movingPiece == wK; if (validMove(isWhite, row + 1, column)) { - moves.push_back((boardPos) { + boardPos tPos = (boardPos) { row + 1, column - }); + }; + bool possibleKingMove = true; + for (int i = 0; i < 64; i++) { + Piece possibleThreat = getPiece(i/8, i%8); + if (isWhite && (possibleThreat == bK || possibleThreat == bQ || possibleThreat == bR || possibleThreat == bN || possibleThreat == bB || possibleThreat == b)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } else if (!isWhite && (possibleThreat == wK || possibleThreat == wQ || possibleThreat == wR || possibleThreat == wN || possibleThreat == wB || possibleThreat == w)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } + } + if (possibleKingMove) { + moves.push_back(tPos); + } } if (validMove(isWhite, row, column + 1)) { - moves.push_back((boardPos) { + boardPos tPos = (boardPos) { row, column + 1 - }); + }; + bool possibleKingMove = true; + for (int i = 0; i < 64; i++) { + Piece possibleThreat = getPiece(i/8, i%8); + if (isWhite && (possibleThreat == bK || possibleThreat == bQ || possibleThreat == bR || possibleThreat == bN || possibleThreat == bB || possibleThreat == b)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } else if (!isWhite && (possibleThreat == wK || possibleThreat == wQ || possibleThreat == wR || possibleThreat == wN || possibleThreat == wB || possibleThreat == w)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } + } + if (possibleKingMove) { + moves.push_back(tPos); + } } if (validMove(isWhite, row - 1, column)) { - moves.push_back((boardPos) { + boardPos tPos = (boardPos) { row - 1, column - }); + }; + bool possibleKingMove = true; + for (int i = 0; i < 64; i++) { + Piece possibleThreat = getPiece(i/8, i%8); + if (isWhite && (possibleThreat == bK || possibleThreat == bQ || possibleThreat == bR || possibleThreat == bN || possibleThreat == bB || possibleThreat == b)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } else if (!isWhite && (possibleThreat == wK || possibleThreat == wQ || possibleThreat == wR || possibleThreat == wN || possibleThreat == wB || possibleThreat == w)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } + } + if (possibleKingMove) { + moves.push_back(tPos); + } } if (validMove(isWhite, row, column - 1)) { - moves.push_back((boardPos) { + boardPos tPos = (boardPos) { row, column - 1 - }); + }; + bool possibleKingMove = true; + for (int i = 0; i < 64; i++) { + Piece possibleThreat = getPiece(i/8, i%8); + if (isWhite && (possibleThreat == bK || possibleThreat == bQ || possibleThreat == bR || possibleThreat == bN || possibleThreat == bB || possibleThreat == b)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } else if (!isWhite && (possibleThreat == wK || possibleThreat == wQ || possibleThreat == wR || possibleThreat == wN || possibleThreat == wB || possibleThreat == w)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } + } + if (possibleKingMove) { + moves.push_back(tPos); + } } if (validMove(isWhite, row + 1, column + 1)) { - moves.push_back((boardPos) { + boardPos tPos = (boardPos) { row + 1, column + 1 - }); + }; + bool possibleKingMove = true; + for (int i = 0; i < 64; i++) { + Piece possibleThreat = getPiece(i/8, i%8); + if (isWhite && (possibleThreat == bK || possibleThreat == bQ || possibleThreat == bR || possibleThreat == bN || possibleThreat == bB || possibleThreat == b)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } else if (!isWhite && (possibleThreat == wK || possibleThreat == wQ || possibleThreat == wR || possibleThreat == wN || possibleThreat == wB || possibleThreat == w)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } + } + if (possibleKingMove) { + moves.push_back(tPos); + } } if (validMove(isWhite, row - 1, column + 1)) { - moves.push_back((boardPos) { + boardPos tPos = (boardPos) { row - 1, column + 1 - }); + }; + bool possibleKingMove = true; + for (int i = 0; i < 64; i++) { + Piece possibleThreat = getPiece(i/8, i%8); + if (isWhite && (possibleThreat == bK || possibleThreat == bQ || possibleThreat == bR || possibleThreat == bN || possibleThreat == bB || possibleThreat == b)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } else if (!isWhite && (possibleThreat == wK || possibleThreat == wQ || possibleThreat == wR || possibleThreat == wN || possibleThreat == wB || possibleThreat == w)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } + } + if (possibleKingMove) { + moves.push_back(tPos); + } } if (validMove(isWhite, row - 1, column - 1)) { - moves.push_back((boardPos) { + boardPos tPos = (boardPos) { row - 1, column - 1 - }); + }; + bool possibleKingMove = true; + for (int i = 0; i < 64; i++) { + Piece possibleThreat = getPiece(i/8, i%8); + if (isWhite && (possibleThreat == bK || possibleThreat == bQ || possibleThreat == bR || possibleThreat == bN || possibleThreat == bB || possibleThreat == b)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } else if (!isWhite && (possibleThreat == wK || possibleThreat == wQ || possibleThreat == wR || possibleThreat == wN || possibleThreat == wB || possibleThreat == w)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } + } + if (possibleKingMove) { + moves.push_back(tPos); + } } if (validMove(isWhite, row + 1, column - 1)) { - moves.push_back((boardPos) { + boardPos tPos = (boardPos) { row + 1, column - 1 - }); + }; + bool possibleKingMove = true; + for (int i = 0; i < 64; i++) { + Piece possibleThreat = getPiece(i/8, i%8); + if (isWhite && (possibleThreat == bK || possibleThreat == bQ || possibleThreat == bR || possibleThreat == bN || possibleThreat == bB || possibleThreat == b)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } else if (!isWhite && (possibleThreat == wK || possibleThreat == wQ || possibleThreat == wR || possibleThreat == wN || possibleThreat == wB || possibleThreat == w)) { + std::vector<boardPos> possibleThreatMoves = getMoves(i/8, i%8); + if (std::find(possibleThreatMoves.begin(), possibleThreatMoves.end(), tPos) != possibleThreatMoves.end()) { + possibleKingMove = false; + break; + } + } + } + if (possibleKingMove) { + moves.push_back(tPos); + } } break; case wQ: @@ -855,6 +1126,7 @@ { 3, 4 }; +bool OnePlayer = true; boardPos selectedPos; Piece selectedPiece; std::vector<boardPos> possibleMoves; @@ -926,7 +1198,11 @@ } gameBoard.selectSquare(selectedPos); // transition state - state = state == whiteSelecting ? whitePickedUp : blackPickedUp; + if (OnePlayer) { + state = whitePickedUp; + } else { + state = state == whiteSelecting ? whitePickedUp : blackPickedUp; + } } else { selectedPos = (boardPos) { @@ -946,7 +1222,11 @@ // game end } // transition state - state = state == whitePickedUp ? blackSelecting : whiteSelecting; + if (OnePlayer) { + state = blackAI; + } else { + state = state == whitePickedUp ? blackSelecting : whiteSelecting; + } // check if placing piece back down } else { // transition state @@ -966,9 +1246,124 @@ break; } case whiteAI: + break; case blackAI: { + boardPos bestMoveSourceDepth0 = (boardPos) { + 0, 0 + }; + boardPos bestMoveDestDepth0 = (boardPos) { + 0, 0 + }; + float bestMoveValueDepth0 = 100000.0; + for (int i = 0; i < 64; i++) { + boardPos currSourceDepth0 = (boardPos) { + i/8, i%8 + }; + Piece currPieceDepth0 = gameBoard.getBoardState().getPiece(i/8, i%8); + if (currPieceDepth0 == bK || currPieceDepth0 == bQ || currPieceDepth0 == bR || currPieceDepth0 == bB || currPieceDepth0 == bN || currPieceDepth0 == b) { + std::vector<boardPos> possibleMovesDepth0 = gameBoard.getBoardState().getMoves(currSourceDepth0); + for (std::vector<boardPos>::iterator it = possibleMovesDepth0.begin(); it != possibleMovesDepth0.end(); ++it) { + BoardState bsDepth0; + for (int j = 0; j < 64; j++) { + bsDepth0.placePiece(gameBoard.getBoardState().getPiece(j/8, j%8), j/8, j%8); + } + bsDepth0.movePiece(currSourceDepth0.row, currSourceDepth0.column, (*it).row, (*it).column); + float bestMoveValueDepth1 = -100000.0; + for (int i2 = 0; i2 < 64; i2++) { + boardPos currSourceDepth1 = (boardPos) { + i2/8, i2%8 + }; + Piece currPieceDepth1 = bsDepth0.getPiece(i2/8, i2%8); + if (currPieceDepth1 == wK || currPieceDepth1 == wQ || currPieceDepth1 == wR || currPieceDepth1 == wB || currPieceDepth1 == wN || currPieceDepth1 == w) { + std::vector<boardPos> possibleMovesDepth1 = bsDepth0.getMoves(currSourceDepth1); + for (std::vector<boardPos>::iterator it2 = possibleMovesDepth1.begin(); it2 != possibleMovesDepth1.end(); ++it2) { + BoardState bsDepth1; + for (int j2 = 0; j2 < 64; j2++) { + bsDepth1.placePiece(bsDepth0.getPiece(j2/8, j2%8), j2/8, j2%8); + } + bsDepth1.movePiece(currSourceDepth1.row, currSourceDepth1.column, (*it2).row, (*it2).column); + float bestMoveValueDepth2 = 100000.0; + for (int i3 = 0; i3 < 64; i3++) { + boardPos currSourceDepth2 = (boardPos) { + i3/8, i3%8 + }; + Piece currPieceDepth2 = bsDepth1.getPiece(i3/8, i3%8); + if (currPieceDepth2 == bK || currPieceDepth2 == bQ || currPieceDepth2 == bR || currPieceDepth2 == bB || currPieceDepth2 == bN || currPieceDepth2 == b) { + std::vector<boardPos> possibleMovesDepth2 = bsDepth1.getMoves(currSourceDepth2); + for (std::vector<boardPos>::iterator it3 = possibleMovesDepth2.begin(); it3 != possibleMovesDepth2.end(); ++it3) { + BoardState bsDepth2; + for (int j3 = 0; j3 < 64; j3++) { + bsDepth2.placePiece(bsDepth1.getPiece(j3/8, j3%8), j3/8, j3%8); + } + bsDepth2.movePiece(currSourceDepth2.row, currSourceDepth2.column, (*it3).row, (*it3).column); + float thisMoveValueDepth2 = bsDepth2.calculateBoardState(); + if (thisMoveValueDepth2 < bestMoveValueDepth2) { + bestMoveValueDepth2 = thisMoveValueDepth2; + } + } + } + } + if (bestMoveValueDepth2 > bestMoveValueDepth1) { + bestMoveValueDepth1 = bestMoveValueDepth2; + } + } + } + } + + if (bestMoveValueDepth1 < bestMoveValueDepth0) { + bestMoveSourceDepth0 = currSourceDepth0; + bestMoveDestDepth0 = *it; + bestMoveValueDepth0 = bestMoveValueDepth1; + } + } + } + } + Piece capturedPiece = gameBoard.movePieceAndDraw(bestMoveSourceDepth0, bestMoveDestDepth0); + if (capturedPiece == bK) { + // game end + } + state = whiteSelecting; break; } + default: { + break; + } + } +} + +// bluetooth +volatile bool button_ready = 0; +volatile int bnum = 0; +volatile int bhit = 0; +enum statetype {start = 0, got_exclm, got_B, got_num, got_hit}; +statetype bluetooth_state = start; + +void parse_message() +{ + switch (bluetooth_state) { + case start: + if (Blue.getc() == '!') bluetooth_state = got_exclm; + else bluetooth_state = start; + break; + case got_exclm: + if (Blue.getc() == 'B') bluetooth_state = got_B; + else bluetooth_state = start; + break; + case got_B: + bnum = Blue.getc(); + bluetooth_state = got_num; + break; + case got_num: + bhit = Blue.getc(); + bluetooth_state = got_hit; + break; + case got_hit: + if (Blue.getc() == char(~('!' + ' B' + bnum + bhit))) button_ready = 1; + bluetooth_state = start; + break; + default: + Blue.getc(); + bluetooth_state = start; } } @@ -990,7 +1385,11 @@ blackPieces.push_back(bR); blackPieces.push_back(b); moveCursor(0, 0); + Blue.attach(&parse_message,Serial::RxIrq); while (1) { + if (state == blackAI) { + joyStickPressed(); + } if (myNav.up()) { joyStickUp(); } else if (myNav.down()) { @@ -1001,7 +1400,26 @@ joyStickRight(); } else if (myNav.fire()) { joyStickPressed(); + } else if (button_ready && bhit == '1') { + switch(bnum) { + case '1': + joyStickPressed(); + break; + case '5': + joyStickUp(); + break; + case '6': + joyStickDown(); + break; + case '7': + joyStickLeft(); + break; + case '8': + joyStickRight(); + break; + } + button_ready = false; } - wait(0.25); + wait(0.2); } } \ No newline at end of file